@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/display/index.ts"],"sourcesContent":["/**\n * Result rendering, mirroring ScaleDP's notebook display helpers.\n *\n * Python monkey-patches `show_image`, `show_text`, `show_ner` and\n * `visualize_ner` onto the Spark DataFrame and renders Jinja templates into\n * IPython. The browser equivalent is to build DOM elements, so these return an\n * `HTMLElement` the caller places wherever it likes -- and `renderInto` is the\n * one-liner for the common case.\n *\n * Everything is built with text nodes rather than interpolated markup:\n * recognized text and entity words come straight from the document, and a page\n * containing `<` would otherwise corrupt the DOM.\n */\n\nimport type { Box } from '../schemas/box.js'\nimport type { DetectorOutput } from '../schemas/detector-output.js'\nimport type { Document } from '../schemas/document.js'\nimport type { Entity, NerOutput } from '../schemas/entity.js'\nimport type { ScaleDpImage } from '../schemas/image.js'\n\n/**\n * A stable colour per entity group.\n *\n * Python picks a random colour each run, so two renders of the same document\n * never match. Hashing the group name keeps 'PERSON' one colour everywhere.\n */\nexport function colorForGroup(name: string): string {\n let hash = 0\n for (let i = 0; i < name.length; i++) hash = (hash * 31 + name.charCodeAt(i)) | 0\n return `hsl(${Math.abs(hash) % 360}, 70%, 45%)`\n}\n\nfunction element<K extends keyof HTMLElementTagNameMap>(\n tag: K,\n props: { text?: string; className?: string; style?: Partial<CSSStyleDeclaration> } = {}\n): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag)\n if (props.text !== undefined) node.textContent = props.text\n if (props.className) node.className = props.className\n if (props.style) Object.assign(node.style, props.style)\n return node\n}\n\n/** Replace a container's contents with `node`. Accepts a selector or element. */\nexport function renderInto(target: string | HTMLElement, node: Node): HTMLElement {\n const host = typeof target === 'string' ? document.querySelector<HTMLElement>(target) : target\n if (!host) throw new Error(`No element matches ${String(target)}`)\n host.replaceChildren(node)\n return host\n}\n\nexport interface ShowImageOptions {\n /** CSS width, e.g. '600px' or '100%'. */\n width?: string\n alt?: string\n}\n\n/**\n * An `<img>` for a ScaleDP image. Mirrors `show_image`.\n *\n * The object URL is revoked once the image has decoded -- holding one per page\n * leaks the whole blob for the lifetime of the document.\n */\nexport function showImage(image: ScaleDpImage, options: ShowImageOptions = {}): HTMLElement {\n if (image.exception) return errorBlock(image.exception)\n\n const bytes = new Uint8Array(image.data.byteLength)\n bytes.set(image.data)\n const url = URL.createObjectURL(new Blob([bytes.buffer], { type: `image/${image.imageType}` }))\n\n const img = element('img', { style: { maxWidth: options.width ?? '100%', height: 'auto' } })\n img.alt = options.alt ?? image.path\n img.addEventListener('load', () => URL.revokeObjectURL(url), { once: true })\n img.addEventListener('error', () => URL.revokeObjectURL(url), { once: true })\n img.src = url\n return img\n}\n\nexport interface ShowTextOptions {\n /**\n * Preserve the document's own layout. Correct when the OCR stage ran with\n * `keepFormatting`, which encodes the layout in spaces and blank lines.\n */\n preserveLayout?: boolean\n maxHeight?: string\n}\n\n/** A `<pre>` of the recognized text. Mirrors `show_text`. */\nexport function showText(document_: Document, options: ShowTextOptions = {}): HTMLElement {\n if (document_.exception) return errorBlock(document_.exception)\n\n return element('pre', {\n text: document_.text,\n className: 'scaledp-text',\n style: {\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: '12px',\n whiteSpace: options.preserveLayout === false ? 'pre-wrap' : 'pre',\n overflowX: 'auto',\n maxHeight: options.maxHeight ?? '30rem',\n margin: '0',\n },\n })\n}\n\n/** Pretty-printed JSON. Mirrors `show_json`. */\nexport function showJson(value: unknown, indent = 2): HTMLElement {\n const text = typeof value === 'string' ? value : JSON.stringify(value, null, indent)\n return element('pre', {\n text,\n className: 'scaledp-json',\n style: { fontFamily: 'ui-monospace, monospace', fontSize: '12px', overflowX: 'auto' },\n })\n}\n\nexport interface ShowNerOptions {\n /** Maximum rows; 0 shows all. Python defaults to 20. */\n limit?: number\n /** Only these groups. */\n whiteList?: readonly string[]\n}\n\n/** A table of entities. Mirrors `show_ner`. */\nexport function showNer(ner: NerOutput, options: ShowNerOptions = {}): HTMLElement {\n if (ner.exception) return errorBlock(ner.exception)\n\n const allowed = new Set(options.whiteList ?? [])\n let entities = allowed.size > 0 ? ner.entities.filter((e) => allowed.has(e.entity_group)) : ner.entities\n\n const limit = options.limit ?? 20\n const total = entities.length\n if (limit > 0) entities = entities.slice(0, limit)\n\n if (total === 0) return element('p', { text: 'No entities found.' })\n\n const table = element('table', { className: 'scaledp-ner' })\n table.style.borderCollapse = 'collapse'\n\n const header = table.insertRow()\n for (const label of ['Type', 'Text', 'Score', 'Start', 'End', 'Boxes']) {\n const th = document.createElement('th')\n th.textContent = label\n th.style.cssText = 'border:1px solid #ddd;padding:4px 8px;text-align:left'\n header.append(th)\n }\n\n for (const entity of entities) {\n const tr = table.insertRow()\n const swatch = element('span', {\n style: {\n display: 'inline-block',\n width: '8px',\n height: '8px',\n borderRadius: '50%',\n marginRight: '6px',\n background: colorForGroup(entity.entity_group),\n },\n })\n const cells: (string | Node)[] = [\n entity.entity_group,\n entity.word,\n entity.score.toFixed(3),\n String(entity.start),\n String(entity.end),\n String(entity.boxes.length),\n ]\n cells.forEach((value, index) => {\n const cell = tr.insertCell()\n cell.style.cssText = 'border:1px solid #ddd;padding:4px 8px'\n if (index === 0) cell.append(swatch)\n cell.append(typeof value === 'string' ? document.createTextNode(value) : value)\n })\n }\n\n const wrapper = element('div')\n wrapper.append(table)\n if (limit > 0 && total > limit) {\n wrapper.append(element('p', { text: `Showing ${limit} of ${total} entities.` }))\n }\n return wrapper\n}\n\nexport interface VisualizeNerOptions {\n /** Only highlight these groups. */\n labelsList?: readonly string[]\n /** Render the group name beside each highlight. */\n showLabels?: boolean\n}\n\n/**\n * The document text with entities highlighted inline. Mirrors `visualize_ner`.\n *\n * Splices spans by character offset, which is exactly what `Entity.start`/`end`\n * index. Overlapping entities are dropped rather than nested: the highest-\n * scoring one wins, because two spans cannot occupy the same characters in a\n * flat text run.\n */\nexport function visualizeNer(\n document_: Document,\n ner: NerOutput,\n options: VisualizeNerOptions = {}\n): HTMLElement {\n if (document_.exception) return errorBlock(document_.exception)\n if (ner.exception) return errorBlock(ner.exception)\n\n const allowed = new Set(options.labelsList ?? [])\n const entities = (\n allowed.size > 0 ? ner.entities.filter((e) => allowed.has(e.entity_group)) : ner.entities\n )\n .filter((e) => e.start >= 0 && e.end > e.start && e.end <= document_.text.length)\n .sort((a, b) => a.start - b.start || b.score - a.score)\n\n const container = element('div', {\n className: 'scaledp-ner-text',\n style: {\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: '12px',\n whiteSpace: 'pre-wrap',\n lineHeight: '1.9',\n },\n })\n\n let cursor = 0\n for (const entity of entities) {\n if (entity.start < cursor) continue // overlaps an already-rendered span\n\n if (entity.start > cursor) {\n container.append(document.createTextNode(document_.text.slice(cursor, entity.start)))\n }\n\n const color = colorForGroup(entity.entity_group)\n const mark = element('span', {\n text: document_.text.slice(entity.start, entity.end),\n style: {\n background: color,\n color: '#fff',\n borderRadius: '3px',\n padding: '1px 3px',\n },\n })\n mark.title = `${entity.entity_group} (${entity.score.toFixed(3)})`\n container.append(mark)\n\n if (options.showLabels) {\n container.append(\n element('sup', {\n text: entity.entity_group,\n style: { color, fontSize: '9px', marginLeft: '2px' },\n })\n )\n }\n cursor = entity.end\n }\n container.append(document.createTextNode(document_.text.slice(cursor)))\n return container\n}\n\n/** A summary table of detected boxes. */\nexport function showBoxes(output: DetectorOutput | Document, limit = 20): HTMLElement {\n if (output.exception) return errorBlock(output.exception)\n\n const boxes: Box[] = output.bboxes\n const table = element('table')\n table.style.borderCollapse = 'collapse'\n\n const header = table.insertRow()\n for (const label of ['Text', 'Score', 'x', 'y', 'w', 'h', 'angle']) {\n const th = document.createElement('th')\n th.textContent = label\n th.style.cssText = 'border:1px solid #ddd;padding:4px 8px;text-align:left'\n header.append(th)\n }\n\n for (const box of limit > 0 ? boxes.slice(0, limit) : boxes) {\n const tr = table.insertRow()\n for (const value of [\n box.text,\n box.score.toFixed(3),\n String(box.x),\n String(box.y),\n String(box.width),\n String(box.height),\n box.angle.toFixed(1),\n ]) {\n const cell = tr.insertCell()\n cell.style.cssText = 'border:1px solid #ddd;padding:4px 8px'\n cell.textContent = value\n }\n }\n\n const wrapper = element('div')\n wrapper.append(table)\n if (limit > 0 && boxes.length > limit) {\n wrapper.append(element('p', { text: `Showing ${limit} of ${boxes.length} boxes.` }))\n }\n return wrapper\n}\n\nfunction errorBlock(message: string): HTMLElement {\n return element('pre', {\n text: message,\n style: {\n color: '#b00020',\n whiteSpace: 'pre-wrap',\n fontFamily: 'ui-monospace, monospace',\n fontSize: '12px',\n },\n })\n}\n\nexport type { DetectorOutput, Document, Entity, NerOutput, ScaleDpImage }\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { n as Box } from "./box-DAfzwfhA.js";
2
+ //#region src/schemas/document.d.ts
3
+ interface Document {
4
+ path: string;
5
+ text: string;
6
+ /** Producer of this document: 'text' | 'ocr' | 'pdf' | an engine name. */
7
+ type: string;
8
+ bboxes: Box[];
9
+ exception: string;
10
+ }
11
+ declare function createDocument(init?: Partial<Document>): Document;
12
+ /** Python's `Document.merge` puts the *argument* first and joins with a newline. */
13
+ declare function mergeDocuments(self: Document, other: Document): Document;
14
+ //#endregion
15
+ export { createDocument as n, mergeDocuments as r, Document as t };
16
+ //# sourceMappingURL=document-B8I61TiY.d.ts.map
@@ -0,0 +1,22 @@
1
+ import { n as Box } from "./box-DAfzwfhA.js";
2
+ //#region src/schemas/entity.d.ts
3
+ interface Entity {
4
+ entity_group: string;
5
+ score: number;
6
+ word: string;
7
+ /** Character offset into the source document text. */
8
+ start: number;
9
+ end: number;
10
+ /** Boxes the entity's characters fall inside; empty when there is no OCR layer. */
11
+ boxes: Box[];
12
+ }
13
+ interface NerOutput {
14
+ path: string;
15
+ entities: Entity[];
16
+ exception: string;
17
+ json: string;
18
+ }
19
+ declare function createNerOutput(init?: Partial<NerOutput>): NerOutput;
20
+ //#endregion
21
+ export { NerOutput as n, createNerOutput as r, Entity as t };
22
+ //# sourceMappingURL=entity-CedtRhU1.d.ts.map
@@ -0,0 +1,13 @@
1
+ //#region src/schemas/entity.ts
2
+ function createNerOutput(init = {}) {
3
+ return {
4
+ path: init.path ?? "memory",
5
+ entities: init.entities ?? [],
6
+ exception: init.exception ?? "",
7
+ json: init.json ?? ""
8
+ };
9
+ }
10
+ //#endregion
11
+ export { createNerOutput as t };
12
+
13
+ //# sourceMappingURL=entity-D6Hxaugj.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entity-D6Hxaugj.js","names":[],"sources":["../src/schemas/entity.ts"],"sourcesContent":["/** Port of `scaledp/schemas/Entity.py` and `NerOutput.py`. */\n\nimport type { Box } from './box.js'\n\nexport interface Entity {\n entity_group: string\n score: number\n word: string\n /** Character offset into the source document text. */\n start: number\n end: number\n /** Boxes the entity's characters fall inside; empty when there is no OCR layer. */\n boxes: Box[]\n}\n\nexport interface NerOutput {\n path: string\n entities: Entity[]\n exception: string\n json: string\n}\n\nexport function createNerOutput(init: Partial<NerOutput> = {}): NerOutput {\n return {\n path: init.path ?? 'memory',\n entities: init.entities ?? [],\n exception: init.exception ?? '',\n json: init.json ?? '',\n }\n}\n"],"mappings":";AAsBA,SAAgB,gBAAgB,OAA2B,CAAC,GAAc;CACtE,OAAO;EACH,MAAM,KAAK,QAAQ;EACnB,UAAU,KAAK,YAAY,CAAC;EAC5B,WAAW,KAAK,aAAa;EAC7B,MAAM,KAAK,QAAQ;CACvB;AACJ"}
@@ -0,0 +1,511 @@
1
+ //#region src/core/geometry.ts
2
+ const EPSILON = 1e-9;
3
+ /** Cross product of (o->a) and (o->b). > 0 means counter-clockwise. */
4
+ function cross(o, a, b) {
5
+ return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
6
+ }
7
+ /**
8
+ * Monotone-chain convex hull. Returns hull points counter-clockwise in a
9
+ * y-down image coordinate system, without the duplicated closing point.
10
+ */
11
+ function convexHull(points) {
12
+ const pts = [...points].sort((p, q) => p[0] === q[0] ? p[1] - q[1] : p[0] - q[0]);
13
+ const uniq = [];
14
+ for (const p of pts) {
15
+ const last = uniq[uniq.length - 1];
16
+ if (!last || last[0] !== p[0] || last[1] !== p[1]) uniq.push(p);
17
+ }
18
+ if (uniq.length < 3) return uniq;
19
+ const build = (source) => {
20
+ const chain = [];
21
+ for (const p of source) {
22
+ while (chain.length >= 2 && cross(chain[chain.length - 2], chain[chain.length - 1], p) <= 0) chain.pop();
23
+ chain.push(p);
24
+ }
25
+ chain.pop();
26
+ return chain;
27
+ };
28
+ return [...build(uniq), ...build([...uniq].reverse())];
29
+ }
30
+ /**
31
+ * Minimum-area enclosing rectangle via rotating calipers.
32
+ *
33
+ * The minimum-area rectangle always has a side flush with a convex-hull edge,
34
+ * so testing one orientation per hull edge is exhaustive.
35
+ */
36
+ function minAreaRect(points) {
37
+ if (points.length === 0) return {
38
+ center: [0, 0],
39
+ size: [0, 0],
40
+ angle: 0
41
+ };
42
+ const hull = convexHull(points);
43
+ if (hull.length < 2) {
44
+ const p = hull[0] ?? points[0];
45
+ return {
46
+ center: [p[0], p[1]],
47
+ size: [0, 0],
48
+ angle: 0
49
+ };
50
+ }
51
+ let best = null;
52
+ for (let i = 0; i < hull.length; i++) {
53
+ const a = hull[i];
54
+ const b = hull[(i + 1) % hull.length];
55
+ const len = Math.hypot(b[0] - a[0], b[1] - a[1]);
56
+ if (len < EPSILON) continue;
57
+ const ux = (b[0] - a[0]) / len;
58
+ const uy = (b[1] - a[1]) / len;
59
+ const vx = -uy;
60
+ const vy = ux;
61
+ let minU = Infinity;
62
+ let maxU = -Infinity;
63
+ let minV = Infinity;
64
+ let maxV = -Infinity;
65
+ for (const p of hull) {
66
+ const pu = p[0] * ux + p[1] * uy;
67
+ const pv = p[0] * vx + p[1] * vy;
68
+ if (pu < minU) minU = pu;
69
+ if (pu > maxU) maxU = pu;
70
+ if (pv < minV) minV = pv;
71
+ if (pv > maxV) maxV = pv;
72
+ }
73
+ const w = maxU - minU;
74
+ const h = maxV - minV;
75
+ const area = w * h;
76
+ if (best === null || area < best.area) {
77
+ const cu = (minU + maxU) / 2;
78
+ const cv = (minV + maxV) / 2;
79
+ best = {
80
+ area,
81
+ center: [cu * ux + cv * vx, cu * uy + cv * vy],
82
+ w,
83
+ h,
84
+ angle: Math.atan2(uy, ux) * 180 / Math.PI
85
+ };
86
+ }
87
+ }
88
+ if (best === null) return {
89
+ center: [0, 0],
90
+ size: [0, 0],
91
+ angle: 0
92
+ };
93
+ let { angle, w, h } = best;
94
+ while (angle <= 0) {
95
+ angle += 90;
96
+ [w, h] = [h, w];
97
+ }
98
+ while (angle > 90) {
99
+ angle -= 90;
100
+ [w, h] = [h, w];
101
+ }
102
+ return {
103
+ center: best.center,
104
+ size: [w, h],
105
+ angle
106
+ };
107
+ }
108
+ /**
109
+ * The 4 corners of a rotated rect, in cv2.boxPoints order.
110
+ * For an upright rect in image coordinates that is: BL, TL, TR, BR.
111
+ */
112
+ function boxPoints(rect) {
113
+ const [cx, cy] = rect.center;
114
+ const [w, h] = rect.size;
115
+ const rad = rect.angle * Math.PI / 180;
116
+ const b = Math.cos(rad) * .5;
117
+ const a = Math.sin(rad) * .5;
118
+ const p0 = [cx - a * h - b * w, cy + b * h - a * w];
119
+ const p1 = [cx + a * h - b * w, cy - b * h - a * w];
120
+ return [
121
+ p0,
122
+ p1,
123
+ [2 * cx - p0[0], 2 * cy - p0[1]],
124
+ [2 * cx - p1[0], 2 * cy - p1[1]]
125
+ ];
126
+ }
127
+ /** Shoelace area of a simple polygon; always non-negative. */
128
+ function polygonArea(points) {
129
+ let sum = 0;
130
+ for (let i = 0; i < points.length; i++) {
131
+ const a = points[i];
132
+ const b = points[(i + 1) % points.length];
133
+ sum += a[0] * b[1] - b[0] * a[1];
134
+ }
135
+ return Math.abs(sum) / 2;
136
+ }
137
+ /** Perimeter of a closed polygon. */
138
+ function polygonPerimeter(points) {
139
+ let sum = 0;
140
+ for (let i = 0; i < points.length; i++) {
141
+ const a = points[i];
142
+ const b = points[(i + 1) % points.length];
143
+ sum += Math.hypot(b[0] - a[0], b[1] - a[1]);
144
+ }
145
+ return sum;
146
+ }
147
+ //#endregion
148
+ //#region src/schemas/box.ts
149
+ /**
150
+ * Port of `scaledp/schemas/Box.py`.
151
+ *
152
+ * A Box is NOT xyxy and NOT a polygon. `x`/`y` is the top-left of the
153
+ * *axis-aligned* box of the same size centred on the rotated rect's centre,
154
+ * and `angle` is degrees about that same centre. `width` is always the longer
155
+ * side. Getting this wrong silently shifts every downstream consumer, so the
156
+ * conversions below mirror the Python implementation exactly.
157
+ */
158
+ /** `abs(angle) >= 3` — matches Python's `is_rotated`, which tolerates OCR jitter. */
159
+ const ROTATION_EPSILON_DEGREES = 3;
160
+ function createBox(init = {}) {
161
+ return {
162
+ text: init.text ?? "",
163
+ score: init.score ?? 0,
164
+ x: init.x ?? 0,
165
+ y: init.y ?? 0,
166
+ width: init.width ?? 0,
167
+ height: init.height ?? 0,
168
+ angle: init.angle ?? 0
169
+ };
170
+ }
171
+ function isRotated(box) {
172
+ return Math.abs(box.angle) >= 3;
173
+ }
174
+ function bbox(box, padding = 0) {
175
+ return [
176
+ box.x - padding,
177
+ box.y - padding,
178
+ box.x + box.width + padding,
179
+ box.y + box.height + padding
180
+ ];
181
+ }
182
+ function shape(box, padding = 0) {
183
+ const [x0, y0, x1, y1] = bbox(box, padding);
184
+ return [[x0, y0], [x1, y1]];
185
+ }
186
+ /**
187
+ * Scale a box, then apply padding.
188
+ *
189
+ * Note the padding is asymmetric, and deliberately so: Python subtracts it from
190
+ * the origin and adds it to the size, so the box grows by `padding` on its left
191
+ * and top while its right and bottom edges stay put. Growing all four sides
192
+ * would need `padding * 2` on the size.
193
+ */
194
+ function scaleBox(box, factor, padding = 0) {
195
+ return {
196
+ ...box,
197
+ x: Math.round(box.x * factor - padding),
198
+ y: Math.round(box.y * factor - padding),
199
+ width: Math.round(box.width * factor + padding),
200
+ height: Math.round(box.height * factor + padding)
201
+ };
202
+ }
203
+ function boxFromBBox(box, opts = {}) {
204
+ const [x0, y0, x1, y1] = box;
205
+ return {
206
+ text: opts.text ?? "",
207
+ score: opts.score ?? 0,
208
+ x: Math.round(x0),
209
+ y: Math.round(y0),
210
+ width: Math.round(x1 - x0),
211
+ height: Math.round(y1 - y0),
212
+ angle: opts.angle ?? 0
213
+ };
214
+ }
215
+ /**
216
+ * Build a Box from exactly 4 polygon points — port of Python `Box.from_polygon`.
217
+ *
218
+ * `width` is forced to be the longer side (subtracting 90 degrees from the angle
219
+ * to compensate), then the angle is normalised to (-90, 270]. `x`/`y` are derived
220
+ * from the centre, NOT from the polygon's bounding box.
221
+ */
222
+ function boxFromPolygon(points, opts = {}) {
223
+ if (points.length !== 4) throw new Error(`boxFromPolygon expects exactly 4 points, received ${points.length}`);
224
+ const padding = opts.padding ?? 0;
225
+ const rect = minAreaRect(points);
226
+ const [cx, cy] = rect.center;
227
+ let [width, height] = rect.size;
228
+ let angle = rect.angle;
229
+ if (width < height) {
230
+ [width, height] = [height, width];
231
+ angle -= 90;
232
+ }
233
+ angle = (angle % 360 + 360) % 360;
234
+ if (angle > 270) angle -= 360;
235
+ width = Math.max(1, Math.round(width) + padding * 2);
236
+ height = Math.max(1, Math.round(height) + padding * 2);
237
+ return {
238
+ text: opts.text ?? "",
239
+ score: opts.score ?? 1,
240
+ x: Math.round(cx - width / 2),
241
+ y: Math.round(cy - height / 2),
242
+ width,
243
+ height,
244
+ angle
245
+ };
246
+ }
247
+ /** Axis-aligned intersection-over-union. Ignores `angle`, exactly as Python does. */
248
+ function boxIou(a, b) {
249
+ const [ax0, ay0, ax1, ay1] = bbox(a);
250
+ const [bx0, by0, bx1, by1] = bbox(b);
251
+ const ix = Math.min(ax1, bx1) - Math.max(ax0, bx0);
252
+ const iy = Math.min(ay1, by1) - Math.max(ay0, by0);
253
+ if (ix <= 0 || iy <= 0) return 0;
254
+ const intersection = ix * iy;
255
+ const union = a.width * a.height + b.width * b.height - intersection;
256
+ return union <= 0 ? 0 : intersection / union;
257
+ }
258
+ /** Union of two boxes. Merging discards rotation — Python resets `angle` to 0. */
259
+ function mergeBoxes(a, b) {
260
+ const [ax0, ay0, ax1, ay1] = bbox(a);
261
+ const [bx0, by0, bx1, by1] = bbox(b);
262
+ const x = Math.min(ax0, bx0);
263
+ const y = Math.min(ay0, by0);
264
+ return {
265
+ text: `${a.text} ${b.text}`.trim(),
266
+ score: Math.min(a.score, b.score),
267
+ x,
268
+ y,
269
+ width: Math.max(ax1, bx1) - x,
270
+ height: Math.max(ay1, by1) - y,
271
+ angle: 0
272
+ };
273
+ }
274
+ /**
275
+ * Whether two boxes sit on the same text line.
276
+ *
277
+ * For near-horizontal boxes this compares vertical centres against the average
278
+ * height. For rotated boxes it projects the centre offset onto the line's normal
279
+ * (dx = -sin, dy = cos), so the test follows the text's own baseline.
280
+ */
281
+ function isOnSameLine(a, b, angleThresh = 10, lineThresh = .5) {
282
+ if (Math.abs(a.angle - b.angle) > angleThresh) return false;
283
+ const avgHeight = (a.height + b.height) / 2;
284
+ if (avgHeight <= 0) return false;
285
+ const acx = a.x + a.width / 2;
286
+ const acy = a.y + a.height / 2;
287
+ const bcx = b.x + b.width / 2;
288
+ const bcy = b.y + b.height / 2;
289
+ if (Math.abs(a.angle) < angleThresh) return Math.abs(acy - bcy) < avgHeight * lineThresh;
290
+ const rad = a.angle * Math.PI / 180;
291
+ const nx = -Math.sin(rad);
292
+ const ny = Math.cos(rad);
293
+ return Math.abs((bcx - acx) * nx + (bcy - acy) * ny) < avgHeight * lineThresh;
294
+ }
295
+ /**
296
+ * Greedily merge boxes that overlap and share a line. Port of Python
297
+ * `Box.merge_overlapping_boxes`.
298
+ *
299
+ * Restarts the scan after each merge so a chain of boxes collapses fully in one
300
+ * call, matching Python's behaviour.
301
+ */
302
+ function mergeOverlappingBoxes(boxes, iouThreshold = .3, angleThresh = 10, lineThresh = .5) {
303
+ const merged = [];
304
+ const used = new Array(boxes.length).fill(false);
305
+ for (let i = 0; i < boxes.length; i++) {
306
+ if (used[i]) continue;
307
+ let current = boxes[i];
308
+ for (let j = i + 1; j < boxes.length; j++) {
309
+ if (used[j]) continue;
310
+ const other = boxes[j];
311
+ if (boxIou(current, other) > iouThreshold && isOnSameLine(current, other, angleThresh, lineThresh)) {
312
+ current = mergeBoxes(current, other);
313
+ used[j] = true;
314
+ }
315
+ }
316
+ merged.push(current);
317
+ used[i] = true;
318
+ }
319
+ return merged;
320
+ }
321
+ //#endregion
322
+ //#region src/core/image.ts
323
+ function assertCanvasSupport() {
324
+ if (typeof OffscreenCanvas === "undefined") throw new Error("OffscreenCanvas is unavailable. scaledp requires a browser or worker context.");
325
+ }
326
+ function createCanvas(width, height) {
327
+ assertCanvasSupport();
328
+ return new OffscreenCanvas(Math.max(1, Math.round(width)), Math.max(1, Math.round(height)));
329
+ }
330
+ function context2d(canvas) {
331
+ const ctx = canvas.getContext("2d", { willReadFrequently: true });
332
+ if (!ctx) throw new Error("Failed to acquire a 2D context");
333
+ return ctx;
334
+ }
335
+ async function decodeImage(data) {
336
+ if (data instanceof Blob) return createImageBitmap(data);
337
+ const bytes = new Uint8Array(data.byteLength);
338
+ bytes.set(data);
339
+ return createImageBitmap(new Blob([bytes.buffer]));
340
+ }
341
+ function toImageData(source) {
342
+ if (source instanceof OffscreenCanvas) return context2d(source).getImageData(0, 0, source.width, source.height);
343
+ const canvas = createCanvas(source.width, source.height);
344
+ context2d(canvas).drawImage(source, 0, 0);
345
+ return context2d(canvas).getImageData(0, 0, canvas.width, canvas.height);
346
+ }
347
+ function imageDataToCanvas(image) {
348
+ const canvas = createCanvas(image.width, image.height);
349
+ context2d(canvas).putImageData(image, 0, 0);
350
+ return canvas;
351
+ }
352
+ async function encodeImage(source, type = "image/png", quality) {
353
+ const blob = await (source instanceof OffscreenCanvas ? source : imageDataToCanvas(source)).convertToBlob({
354
+ type,
355
+ quality
356
+ });
357
+ return new Uint8Array(await blob.arrayBuffer());
358
+ }
359
+ /** Read the intrinsic size of encoded image bytes without keeping the bitmap. */
360
+ async function probeImageSize(data) {
361
+ const bitmap = await decodeImage(data);
362
+ try {
363
+ return {
364
+ width: bitmap.width,
365
+ height: bitmap.height
366
+ };
367
+ } finally {
368
+ bitmap.close();
369
+ }
370
+ }
371
+ /**
372
+ * Fit an image into `target` preserving aspect ratio.
373
+ *
374
+ * `padding: 'end'` pads bottom and right only, matching PaddleOCR's detection
375
+ * preprocessing -- coordinates then restore by dividing by `scale`, with no
376
+ * offset to subtract. `padding: 'center'` centres the image, matching the YOLO
377
+ * preprocessing, where the pad offsets must be subtracted before unscaling.
378
+ */
379
+ function letterbox(source, target, opts = {}) {
380
+ const padding = opts.padding ?? "end";
381
+ const canvas = createCanvas(target.width, target.height);
382
+ const ctx = context2d(canvas);
383
+ ctx.fillStyle = opts.fill ?? "#ffffff";
384
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
385
+ const scale = Math.min(target.width / source.width, target.height / source.height);
386
+ const width = Math.trunc(source.width * scale);
387
+ const height = Math.trunc(source.height * scale);
388
+ const dx = padding === "center" ? Math.trunc((target.width - width) / 2) : 0;
389
+ const dy = padding === "center" ? Math.trunc((target.height - height) / 2) : 0;
390
+ ctx.drawImage(source, dx, dy, width, height);
391
+ return {
392
+ canvas,
393
+ scale,
394
+ resized: {
395
+ width,
396
+ height
397
+ },
398
+ source: {
399
+ width: source.width,
400
+ height: source.height
401
+ }
402
+ };
403
+ }
404
+ /** Uniform resize by a scale factor. */
405
+ function resize(source, factor) {
406
+ const canvas = createCanvas(source.width * factor, source.height * factor);
407
+ context2d(canvas).drawImage(source, 0, 0, canvas.width, canvas.height);
408
+ return canvas;
409
+ }
410
+ function cropGeometry(box, opts = {}) {
411
+ const scaled = scaleBox(box, opts.scaleFactor ?? 1, opts.padding ?? 0);
412
+ const width = Math.max(1, scaled.width);
413
+ const height = Math.max(1, scaled.height);
414
+ const axisAligned = {
415
+ scaled,
416
+ width,
417
+ height,
418
+ map: (x, y) => [scaled.x + x, scaled.y + y]
419
+ };
420
+ if (Math.abs(scaled.angle) < 3) return axisAligned;
421
+ const [, tl, tr, br] = boxPoints({
422
+ center: [scaled.x + width / 2, scaled.y + height / 2],
423
+ size: [width, height],
424
+ angle: scaled.angle
425
+ });
426
+ const bl = [tl[0] + (br[0] - tr[0]), tl[1] + (br[1] - tr[1])];
427
+ const ex = [(tr[0] - tl[0]) / width, (tr[1] - tl[1]) / width];
428
+ const ey = [(bl[0] - tl[0]) / height, (bl[1] - tl[1]) / height];
429
+ const det = ex[0] * ey[1] - ey[0] * ex[1];
430
+ if (Math.abs(det) < 1e-9) return axisAligned;
431
+ return {
432
+ scaled,
433
+ width,
434
+ height,
435
+ map: (x, y) => [tl[0] + x * ex[0] + y * ey[0], tl[1] + x * ex[1] + y * ey[1]]
436
+ };
437
+ }
438
+ function cropBox(source, box, opts = {}) {
439
+ const { scaled, width, height, map } = cropGeometry(box, opts);
440
+ const canvas = createCanvas(width, height);
441
+ const ctx = context2d(canvas);
442
+ const [ox, oy] = map(0, 0);
443
+ const [x1, y1] = map(1, 0);
444
+ const [x2, y2] = map(0, 1);
445
+ const ex = [x1 - ox, y1 - oy];
446
+ const ey = [x2 - ox, y2 - oy];
447
+ if (ex[1] === 0 && ey[0] === 0 && ex[0] === 1 && ey[1] === 1) {
448
+ ctx.drawImage(source, scaled.x, scaled.y, width, height, 0, 0, width, height);
449
+ return canvas;
450
+ }
451
+ const det = ex[0] * ey[1] - ey[0] * ex[1];
452
+ const a = ey[1] / det;
453
+ const b = -ex[1] / det;
454
+ const c = -ey[0] / det;
455
+ const d = ex[0] / det;
456
+ ctx.setTransform(a, b, c, d, -(a * ox + c * oy), -(b * ox + d * oy));
457
+ ctx.drawImage(source, 0, 0);
458
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
459
+ return canvas;
460
+ }
461
+ /**
462
+ * Build an NCHW float32 tensor from an image.
463
+ *
464
+ * `bgr` exists because ScaleDP's DBNet path converts RGB->BGR and never swaps
465
+ * back, so the model is fed BGR channels against RGB ImageNet statistics.
466
+ * Replicating that quirk is required for box parity.
467
+ */
468
+ function toNchwFloat32(image, opts = {}) {
469
+ const { width, height, data } = image;
470
+ const scale = opts.scale ?? 1 / 255;
471
+ const mean = opts.mean ?? [
472
+ 0,
473
+ 0,
474
+ 0
475
+ ];
476
+ const std = opts.std ?? [
477
+ 1,
478
+ 1,
479
+ 1
480
+ ];
481
+ const order = opts.bgr ? [
482
+ 2,
483
+ 1,
484
+ 0
485
+ ] : [
486
+ 0,
487
+ 1,
488
+ 2
489
+ ];
490
+ const plane = width * height;
491
+ const out = new Float32Array(3 * plane);
492
+ for (let i = 0; i < plane; i++) for (let c = 0; c < 3; c++) {
493
+ const value = data[i * 4 + order[c]] * scale;
494
+ out[c * plane + i] = (value - mean[c]) / std[c];
495
+ }
496
+ return out;
497
+ }
498
+ const IMAGENET_MEAN = [
499
+ .485,
500
+ .456,
501
+ .406
502
+ ];
503
+ const IMAGENET_STD = [
504
+ .229,
505
+ .224,
506
+ .225
507
+ ];
508
+ //#endregion
509
+ export { polygonArea as A, mergeBoxes as C, boxPoints as D, shape as E, convexHull as O, isRotated as S, scaleBox as T, boxFromBBox as _, cropBox as a, createBox as b, encodeImage as c, probeImageSize as d, resize as f, bbox as g, ROTATION_EPSILON_DEGREES as h, createCanvas as i, polygonPerimeter as j, minAreaRect as k, imageDataToCanvas as l, toNchwFloat32 as m, IMAGENET_STD as n, cropGeometry as o, toImageData as p, context2d as r, decodeImage as s, IMAGENET_MEAN as t, letterbox as u, boxFromPolygon as v, mergeOverlappingBoxes as w, isOnSameLine as x, boxIou as y };
510
+
511
+ //# sourceMappingURL=image-CAH2rLv9.js.map