@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":"ocr-OHX2WM3e.js","names":["boxesOf"],"sources":["../src/ocr/db-postprocess.ts","../src/ocr/dbnet-onnx.ts","../src/ocr/detector-registry.ts","../src/ocr/line-orientation.ts","../src/ocr/line-orientation-stage.ts","../src/ocr/presets.ts","../src/ocr/paddle-service.ts","../src/ocr/paddle.ts","../src/ocr/script-detect.ts","../src/ocr/tesseract.ts","../src/ocr/tesseract-recognizer.ts"],"sourcesContent":["/**\n * Differentiable Binarization post-processing.\n *\n * Port of `scaledp/models/detectors/paddle_onnx/db_postprocess.py`. Takes the\n * model's probability map and turns it into text quads, with the same\n * thresholds, ordering and filtering as Python so box output matches.\n */\n\nimport { boxPoints, minAreaRect, type Point, polygonArea, polygonPerimeter } from '../core/geometry.js'\n\nexport interface DbPostProcessOptions {\n /** Probability above which a pixel counts as text. */\n thresh: number\n /** Mean in-box probability a candidate must reach to survive. */\n boxThresh: number\n /** Cap on contours considered, guarding against pathological maps. */\n maxCandidates: number\n /** How far to grow each box; DB shrinks regions during training. */\n unclipRatio: number\n /** Minimum short side, in model pixels. */\n minSize: number\n}\n\n/**\n * ScaleDP's own values, from `predict_det.py`. Note `boxThresh` is 0.3 there\n * and the stage-level `scoreThreshold` never reaches this code -- a Python bug\n * that is not reproduced: here `boxThresh` is what the stage actually sets.\n */\nexport const DB_POSTPROCESS_DEFAULTS: DbPostProcessOptions = Object.freeze({\n thresh: 0.5,\n boxThresh: 0.3,\n maxCandidates: 1000,\n unclipRatio: 2.5,\n minSize: 3,\n})\n\nexport interface ProbabilityMap {\n data: Float32Array\n width: number\n height: number\n}\n\nexport interface DetectedQuad {\n /** Four corners, ordered top-left, top-right, bottom-right, bottom-left. */\n points: [Point, Point, Point, Point]\n score: number\n}\n\n/**\n * Boundary pixels of each 8-connected foreground component.\n *\n * cv2.findContours traces outlines; we collect boundary pixels instead. For the\n * only consumers here -- minAreaRect and its convex hull -- the two are\n * equivalent, and this avoids porting Suzuki-Abe border following.\n */\nexport function findComponentBoundaries(\n map: ProbabilityMap,\n thresh: number,\n maxCandidates: number\n): Point[][] {\n const { data, width, height } = map\n const visited = new Uint8Array(width * height)\n const components: Point[][] = []\n\n const isForeground = (x: number, y: number): boolean =>\n x >= 0 && y >= 0 && x < width && y < height && (data[y * width + x] as number) > thresh\n\n const stack: number[] = []\n for (let start = 0; start < visited.length && components.length < maxCandidates; start++) {\n if (visited[start] || !((data[start] as number) > thresh)) continue\n\n const boundary: Point[] = []\n stack.length = 0\n stack.push(start)\n visited[start] = 1\n\n while (stack.length > 0) {\n const index = stack.pop() as number\n const x = index % width\n const y = (index - x) / width\n\n let onBoundary = false\n for (let dy = -1; dy <= 1; dy++) {\n for (let dx = -1; dx <= 1; dx++) {\n if (dx === 0 && dy === 0) continue\n const nx = x + dx\n const ny = y + dy\n if (!isForeground(nx, ny)) {\n onBoundary = true\n continue\n }\n const neighbour = ny * width + nx\n if (!visited[neighbour]) {\n visited[neighbour] = 1\n stack.push(neighbour)\n }\n }\n }\n if (onBoundary) boundary.push([x, y])\n }\n if (boundary.length >= 3) components.push(boundary)\n }\n return components\n}\n\n/**\n * Corners of the minimum-area rect, ordered top-left, top-right, bottom-right,\n * bottom-left, together with the rect's shorter side.\n *\n * Port of `get_mini_boxes`: sort the four corners by x, then decide within each\n * pair which is the upper one.\n */\nexport function miniBox(points: readonly Point[]): { points: [Point, Point, Point, Point]; sside: number } {\n const rect = minAreaRect(points)\n const corners = [...boxPoints(rect)].sort((a, b) => a[0] - b[0])\n\n const [p0, p1, p2, p3] = corners as [Point, Point, Point, Point]\n const [topLeft, bottomLeft] = p0[1] <= p1[1] ? [p0, p1] : [p1, p0]\n const [topRight, bottomRight] = p2[1] <= p3[1] ? [p2, p3] : [p3, p2]\n\n return {\n points: [topLeft, topRight, bottomRight, bottomLeft],\n sside: Math.min(rect.size[0], rect.size[1]),\n }\n}\n\n/**\n * Mean probability inside a quad -- port of `box_score_fast`.\n *\n * Python builds an integer mask with `cv2.fillPoly` and averages the\n * probability map under it, so this reproduces OpenCV's fill convention:\n * vertices are truncated to integers and treated as pixel *centres*, scanlines\n * run at integer y, and both ends of each span are inclusive. Sampling at\n * pixel centres instead (the more usual rasterisation rule) drops the boundary\n * row and column, which shifts the score by around 1% and changes which\n * candidates clear `boxThresh`.\n */\nexport function boxScore(map: ProbabilityMap, points: readonly Point[]): number {\n const { data, width, height } = map\n const xs = points.map((p) => p[0])\n const ys = points.map((p) => p[1])\n\n const clamp = (value: number, max: number) => Math.min(Math.max(value, 0), max)\n const xmin = clamp(Math.floor(Math.min(...xs)), width - 1)\n const xmax = clamp(Math.ceil(Math.max(...xs)), width - 1)\n const ymin = clamp(Math.floor(Math.min(...ys)), height - 1)\n const ymax = clamp(Math.ceil(Math.max(...ys)), height - 1)\n if (xmax < xmin || ymax < ymin) return 0\n\n // Translate into mask space and truncate, matching numpy's astype(int32).\n const local = points.map(([x, y]) => [Math.trunc(x - xmin), Math.trunc(y - ymin)] as Point)\n const maskWidth = xmax - xmin\n const maskHeight = ymax - ymin\n\n let sum = 0\n let count = 0\n for (let y = 0; y <= maskHeight; y++) {\n let left = Number.POSITIVE_INFINITY\n let right = Number.NEGATIVE_INFINITY\n\n for (let i = 0; i < local.length; i++) {\n const a = local[i] as Point\n const b = local[(i + 1) % local.length] as Point\n if (a[1] === b[1]) {\n // A horizontal edge contributes both endpoints on its own row.\n if (a[1] !== y) continue\n left = Math.min(left, a[0], b[0])\n right = Math.max(right, a[0], b[0])\n continue\n }\n if (y < Math.min(a[1], b[1]) || y > Math.max(a[1], b[1])) continue\n const x = a[0] + ((y - a[1]) / (b[1] - a[1])) * (b[0] - a[0])\n left = Math.min(left, x)\n right = Math.max(right, x)\n }\n if (left > right) continue\n\n // The quads here are always convex, so the span between the extreme\n // crossings is exactly the covered run.\n const from = Math.max(0, Math.ceil(left))\n const to = Math.min(maskWidth, Math.floor(right))\n const rowOffset = (y + ymin) * width + xmin\n for (let x = from; x <= to; x++) {\n sum += data[rowOffset + x] as number\n count++\n }\n }\n return count === 0 ? 0 : sum / count\n}\n\n/**\n * Grow a detected rect outward -- port of `unclip`.\n *\n * Python offsets the polygon with a Clipper round join and then takes the\n * minimum-area rect of the result. For a rectangle those two steps compose\n * exactly: offsetting outward by `d` produces a rounded rectangle whose\n * min-area rect is the original grown by `d` on each of the four sides. That\n * identity is what lets this avoid a Clipper dependency entirely.\n */\nexport function unclipRect(points: readonly Point[], unclipRatio: number): [Point, Point, Point, Point] {\n const rect = minAreaRect(points)\n const perimeter = polygonPerimeter(points)\n const distance = perimeter === 0 ? 0 : (polygonArea(points) * unclipRatio) / perimeter\n\n return boxPoints({\n center: rect.center,\n size: [rect.size[0] + distance * 2, rect.size[1] + distance * 2],\n angle: rect.angle,\n })\n}\n\n/**\n * Reorder four points clockwise from the top-left -- port of\n * `order_points_clockwise`. Top-left has the smallest x+y, bottom-right the\n * largest; the remaining two are separated by y-x.\n */\nexport function orderPointsClockwise(points: readonly Point[]): [Point, Point, Point, Point] {\n const bySum = [...points].sort((a, b) => a[0] + a[1] - (b[0] + b[1]))\n const topLeft = bySum[0] as Point\n const bottomRight = bySum[bySum.length - 1] as Point\n\n const rest = bySum.slice(1, -1).sort((a, b) => a[1] - a[0] - (b[1] - b[0]))\n return [topLeft, rest[0] as Point, bottomRight, rest[1] as Point]\n}\n\n/**\n * Probability map -> text quads in source-image coordinates.\n *\n * `scale` is the uniform factor the source was resized by. Coordinates restore\n * by *dividing* by it, with no offset to subtract, because the letterbox pads\n * bottom and right only.\n */\nexport function quadsFromProbabilityMap(\n map: ProbabilityMap,\n source: { width: number; height: number },\n scale: number,\n options: Partial<DbPostProcessOptions> = {}\n): DetectedQuad[] {\n const opts = { ...DB_POSTPROCESS_DEFAULTS, ...options }\n const quads: DetectedQuad[] = []\n\n for (const boundary of findComponentBoundaries(map, opts.thresh, opts.maxCandidates)) {\n const candidate = miniBox(boundary)\n if (candidate.sside < opts.minSize) continue\n\n const score = boxScore(map, candidate.points)\n if (score < opts.boxThresh) continue\n\n const expanded = miniBox(unclipRect(candidate.points, opts.unclipRatio))\n // Python's second gate is `min_size + 2`, i.e. tighter than the first --\n // unclipping can only grow a box, so anything still tiny is noise.\n if (expanded.sside < opts.minSize + 2) continue\n\n // Python clips to [0, dest], inclusive of the far edge -- not [0, dest-1].\n const restored = expanded.points.map(\n ([x, y]) =>\n [\n Math.min(Math.max(Math.round(x / scale), 0), source.width),\n Math.min(Math.max(Math.round(y / scale), 0), source.height),\n ] as Point\n )\n\n const ordered = orderPointsClockwise(restored)\n // Drop slivers: Python filters boxes whose sides are 3px or less.\n const width = Math.hypot(ordered[0][0] - ordered[1][0], ordered[0][1] - ordered[1][1])\n const height = Math.hypot(ordered[0][0] - ordered[3][0], ordered[0][1] - ordered[3][1])\n if (width <= 3 || height <= 3) continue\n\n quads.push({ points: ordered, score })\n }\n return quads\n}\n","/**\n * DBNet ONNX text detection -- the direct mirror of\n * `scaledp/models/detectors/DBNetOnnxDetector.py`, so ScaleDP's own detection\n * model runs unchanged in the browser.\n *\n * Preprocessing reproduces `paddle_onnx/operators.py` exactly, including one\n * quirk that matters: the Python path converts RGB to BGR and never converts\n * back, so the model is fed BGR channels normalised against *RGB* ImageNet\n * statistics. Feeding true RGB instead shifts the boxes.\n *\n * The model detects text *lines*, not words -- one region per line, in Python\n * as here. Nothing downstream subdivides them either: `TesseractRecognizer`\n * reads each region with PSM.SINGLE_WORD and returns one box per region, which\n * is what ScaleDP does too. `TesseractOcr`, which runs tesseract's own layout\n * analysis over the whole page, is the stage that yields word boxes.\n */\n\nimport { getConfig } from '../core/config.js'\nimport { DetectionError } from '../core/errors.js'\nimport {\n decodeImage,\n IMAGENET_MEAN,\n IMAGENET_STD,\n letterbox,\n toImageData,\n toNchwFloat32,\n} from '../core/image.js'\nimport { ensureModelFiles, type ModelSpec } from '../core/model-cache.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage } from '../core/pipeline.js'\nimport { type Box, boxFromPolygon, mergeOverlappingBoxes } from '../schemas/box.js'\nimport { createDetectorOutput, type DetectorOutput } from '../schemas/detector-output.js'\nimport type { ScaleDpImage } from '../schemas/image.js'\nimport { DB_POSTPROCESS_DEFAULTS, type ProbabilityMap, quadsFromProbabilityMap } from './db-postprocess.js'\nimport { createSession } from './ort.js'\n\n/** Fixed input size from ScaleDP's `DetResizeForTest` config. */\nexport const DBNET_INPUT_SIZE = 1280\n\n/** Model ScaleDP's DBNetOnnxDetector documents and its tests use. */\nexport const DEFAULT_DBNET_MODEL = 'StabRise/text_detection_dbnet_ml_v0.2'\n\nexport interface DbnetOnnxDetectorParams extends BaseStageParams {\n /** Hugging Face repo id, or a URL when self-hosting. */\n model: string\n /** Mean in-box probability a candidate must reach. */\n scoreThreshold: number\n /** Probability above which a pixel counts as text. */\n binaryThreshold: number\n /** How far to grow each box; DB shrinks text regions during training. */\n unclipRatio: number\n /**\n * Merge boxes that overlap and share a line. ScaleDP uses an unusually low\n * IoU of 0.02 here, because adjacent words in a line barely overlap.\n *\n * In practice it rarely changes anything for this model: the regions are\n * already whole lines and do not overlap. ScaleDP merges unconditionally\n * and its box count is likewise unchanged. Kept as a parameter because it\n * does matter for detectors that emit overlapping candidates.\n */\n mergeBoxes: boolean\n}\n\nexport const DBNET_DETECTOR_DEFAULTS: DbnetOnnxDetectorParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'image',\n outputCol: 'boxes',\n keepInputData: true,\n model: DEFAULT_DBNET_MODEL,\n scoreThreshold: DB_POSTPROCESS_DEFAULTS.boxThresh,\n binaryThreshold: DB_POSTPROCESS_DEFAULTS.thresh,\n unclipRatio: DB_POSTPROCESS_DEFAULTS.unclipRatio,\n mergeBoxes: true,\n})\n\nfunction modelSpec(model: string): ModelSpec {\n return /^https?:\\/\\//.test(model)\n ? { repo: 'dbnet', files: [{ path: model }] }\n : { repo: model, files: [{ path: 'model.onnx' }] }\n}\n\nexport class DbnetOnnxDetector extends Stage<DbnetOnnxDetectorParams> {\n readonly name = 'DbnetOnnxDetector'\n\n private session: import('onnxruntime-web').InferenceSession | null = null\n private loading: Promise<import('onnxruntime-web').InferenceSession> | null = null\n\n constructor(options: Partial<DbnetOnnxDetectorParams> = {}) {\n super(resolveParams(DBNET_DETECTOR_DEFAULTS, options))\n }\n\n override async init(): Promise<void> {\n await this.getSession()\n }\n\n private getSession(): Promise<import('onnxruntime-web').InferenceSession> {\n if (this.session) return Promise.resolve(this.session)\n if (this.loading) return this.loading\n\n this.loading = (async () => {\n const spec = modelSpec(this.params.model)\n const files = await ensureModelFiles(spec)\n const bytes = files[spec.files[0]?.path ?? '']\n if (!bytes) throw new DetectionError(`Model ${this.params.model} not found`, this.name)\n\n const session = await createSession(bytes, {\n executionProviders: getConfig().executionProviders,\n })\n this.session = session\n return session\n })()\n\n // Clear on failure so a transient network error can be retried rather\n // than cached as a permanently rejected promise.\n this.loading.catch(() => {\n this.loading = null\n })\n return this.loading\n }\n\n override async dispose(): Promise<void> {\n await this.session?.release()\n this.session = null\n this.loading = null\n }\n\n protected async apply(input: unknown, row: Row): Promise<DetectorOutput> {\n const image = input as ScaleDpImage | undefined\n // Check `exception` first. A failed upstream stage returns a well-formed but\n // empty Image, so testing the bytes first would report \"no decoded bytes\" and\n // bury the real cause.\n if (image?.exception) {\n throw new DetectionError(`Upstream stage failed: ${image.exception}`, this.name)\n }\n if (!image || !(image.data instanceof Uint8Array) || image.data.byteLength === 0) {\n throw new DetectionError('Expected an Image with decoded bytes', this.name)\n }\n\n const bitmap = await decodeImage(image.data)\n let boxes: Box[]\n try {\n boxes = await this.detect(bitmap)\n } finally {\n bitmap.close()\n }\n\n return createDetectorOutput({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'dbnet-onnx',\n bboxes: boxes,\n })\n }\n\n /** Run the model over one decoded image and return boxes in its coordinates. */\n async detect(source: ImageBitmap | OffscreenCanvas): Promise<Box[]> {\n const session = await this.getSession()\n\n // Letterbox to a fixed 1280x1280, padding bottom and right only with\n // white. Because nothing is padded at the top or left, coordinates\n // restore by dividing by the scale with no offset to subtract.\n const fitted = letterbox(\n source,\n { width: DBNET_INPUT_SIZE, height: DBNET_INPUT_SIZE },\n {\n padding: 'end',\n fill: '#ffffff',\n }\n )\n const tensorData = toNchwFloat32(toImageData(fitted.canvas), {\n mean: IMAGENET_MEAN,\n std: IMAGENET_STD,\n bgr: true,\n })\n\n const { Tensor } = await import('onnxruntime-web')\n const inputName = session.inputNames[0]\n const outputName = session.outputNames[0]\n if (!inputName || !outputName) {\n throw new DetectionError('Model exposes no input or output', this.name)\n }\n\n const outputs = await session.run({\n [inputName]: new Tensor('float32', tensorData, [1, 3, DBNET_INPUT_SIZE, DBNET_INPUT_SIZE]),\n })\n const output = outputs[outputName]\n if (!output) throw new DetectionError(`Model produced no \"${outputName}\" output`, this.name)\n\n // Output is [N, 1, H, W]; the first channel is the probability map.\n const [, , height = DBNET_INPUT_SIZE, width = DBNET_INPUT_SIZE] = output.dims\n const map: ProbabilityMap = {\n data: output.data as Float32Array,\n width,\n height,\n }\n\n const quads = quadsFromProbabilityMap(\n map,\n { width: source.width, height: source.height },\n fitted.scale,\n {\n thresh: this.params.binaryThreshold,\n boxThresh: this.params.scoreThreshold,\n unclipRatio: this.params.unclipRatio,\n }\n )\n\n const boxes = quads.map((quad) => boxFromPolygon(quad.points, { score: quad.score }))\n return this.params.mergeBoxes ? mergeOverlappingBoxes(boxes, 0.02, 10, 0.3) : boxes\n }\n\n protected onError(message: string, row: Row): DetectorOutput {\n return createDetectorOutput({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'dbnet-onnx',\n exception: message,\n })\n }\n}\n","/**\n * Text detectors that can run in the browser.\n *\n * Detection and recognition are separable in ScaleDP -- a detector stage feeds\n * boxes to a recognizer -- so which detector produced a page's boxes is a real\n * choice worth making addressable, the same way OCR presets and NER models are.\n */\n\nexport type DetectorKind = 'paddle' | 'dbnet-onnx'\n\nexport interface DetectorModel {\n /** Short id callers pass to the picker. */\n id: string\n /** Human-readable name including the download size. */\n name: string\n kind: DetectorKind\n /**\n * Hugging Face repo, for ONNX detectors. Paddle detectors come from the\n * OCR preset instead, so this is unset for them.\n */\n repo?: string\n approxBytes?: number\n notes: string\n}\n\nexport const DETECTOR_MODELS: readonly DetectorModel[] = Object.freeze([\n {\n id: 'paddle',\n name: 'PaddleOCR DB (follows the OCR preset)',\n kind: 'paddle',\n notes: 'Shares the detection model already downloaded for the selected OCR preset.',\n },\n {\n id: 'dbnet-v0.2',\n name: 'StabRise DBNet ONNX v0.2 (~5 MB)',\n kind: 'dbnet-onnx',\n repo: 'StabRise/text_detection_dbnet_ml_v0.2',\n approxBytes: 4_800_000,\n notes: 'The same detection model ScaleDP uses server-side.',\n },\n {\n id: 'dbnet-v0.1',\n name: 'StabRise DBNet ONNX v0.1 (~5 MB)',\n kind: 'dbnet-onnx',\n repo: 'StabRise/text_detection_dbnet_ml_v0.1',\n approxBytes: 4_800_000,\n notes: 'The earlier revision, kept for comparison.',\n },\n])\n\nexport const DEFAULT_DETECTOR_ID = 'paddle'\n\nexport function getDetectorModel(id: string): DetectorModel | undefined {\n return DETECTOR_MODELS.find((m) => m.id === id)\n}\n","/**\n * 0 / 180 degree line-orientation classifier.\n *\n * Port of `scaledp/models/detectors/HasDetectLineOrientation.py`. Tiny, and it\n * meaningfully improves recognition on rotated crops -- an upside-down line\n * otherwise recognises as noise.\n *\n * Reproduces the same BGR-with-RGB-statistics quirk as the DBNet path: the\n * Python code converts to BGR and normalises with RGB ImageNet constants.\n */\n\nimport { DetectionError } from '../core/errors.js'\nimport {\n context2d,\n createCanvas,\n IMAGENET_MEAN,\n IMAGENET_STD,\n toImageData,\n toNchwFloat32,\n} from '../core/image.js'\nimport { ensureModelFiles } from '../core/model-cache.js'\nimport { createSession } from './ort.js'\n\nexport const DEFAULT_ORIENTATION_MODEL = 'StabRise/line_orientation_detection_v0.1'\n\n/** Model input, width x height. */\nexport const ORIENTATION_INPUT = { width: 160, height: 80 } as const\n\nexport type LineOrientation = '0_degree' | '180_degree'\n\nconst LABELS: readonly LineOrientation[] = ['0_degree', '180_degree']\n\nexport class LineOrientationClassifier {\n private session: import('onnxruntime-web').InferenceSession | null = null\n private loading: Promise<import('onnxruntime-web').InferenceSession> | null = null\n\n constructor(readonly model: string = DEFAULT_ORIENTATION_MODEL) {}\n\n private getSession(): Promise<import('onnxruntime-web').InferenceSession> {\n if (this.session) return Promise.resolve(this.session)\n if (this.loading) return this.loading\n\n this.loading = (async () => {\n const files = await ensureModelFiles({\n repo: this.model,\n files: [{ path: 'model.onnx' }],\n })\n const bytes = files['model.onnx']\n if (!bytes) {\n throw new DetectionError(`Model ${this.model} has no model.onnx`, 'LineOrientation')\n }\n const session = await createSession(bytes)\n this.session = session\n return session\n })()\n\n this.loading.catch(() => {\n this.loading = null\n })\n return this.loading\n }\n\n async classify(source: ImageBitmap | OffscreenCanvas): Promise<LineOrientation> {\n const session = await this.getSession()\n\n // Plain resize, not a letterbox: the Python path calls cv2.resize\n // directly and lets the aspect ratio distort.\n const canvas = createCanvas(ORIENTATION_INPUT.width, ORIENTATION_INPUT.height)\n context2d(canvas).drawImage(source, 0, 0, canvas.width, canvas.height)\n\n const data = toNchwFloat32(toImageData(canvas), {\n mean: IMAGENET_MEAN,\n std: IMAGENET_STD,\n bgr: true,\n })\n\n const { Tensor } = await import('onnxruntime-web')\n const inputName = session.inputNames[0]\n const outputName = session.outputNames[0]\n if (!inputName || !outputName) {\n throw new DetectionError('Model exposes no input or output', 'LineOrientation')\n }\n\n const outputs = await session.run({\n [inputName]: new Tensor('float32', data, [\n 1,\n 3,\n ORIENTATION_INPUT.height,\n ORIENTATION_INPUT.width,\n ]),\n })\n const logits = outputs[outputName]?.data as Float32Array | undefined\n if (!logits || logits.length < 2) {\n throw new DetectionError('Classifier produced no logits', 'LineOrientation')\n }\n\n return (logits[1] as number) > (logits[0] as number)\n ? (LABELS[1] as LineOrientation)\n : (LABELS[0] as LineOrientation)\n }\n\n async dispose(): Promise<void> {\n await this.session?.release()\n this.session = null\n this.loading = null\n }\n}\n","/**\n * Line-orientation detection and correction.\n *\n * Port of ScaleDP's `HasDetectLineOrientation`, which `TesseractRecognizer`\n * mixes in: each detected box is cropped, classified 0 or 180 degrees, and the\n * upside-down ones are turned before recognition. An inverted line otherwise\n * recognises as noise.\n *\n * Python does this per crop inside the recognizer. Here it is a stage of its\n * own, because `PaddleTextRecognizer` detects and recognises in a single pass\n * and has no seam to hook into. Flipping each inverted region in place on a\n * copy of the page gets the same result and costs one recognition pass rather\n * than one per box; the regions are rectangles, so a 180-degree turn leaves\n * every box's coordinates untouched and downstream stages need no adjustment.\n */\n\nimport { DetectionError } from '../core/errors.js'\nimport { context2d, createCanvas, cropBox, decodeImage, encodeImage } from '../core/image.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage, type StageContext } from '../core/pipeline.js'\nimport { type Box, isRotated } from '../schemas/box.js'\nimport type { DetectorOutput } from '../schemas/detector-output.js'\nimport type { Document } from '../schemas/document.js'\nimport { createImage, type ImageFormat, type ScaleDpImage } from '../schemas/image.js'\nimport {\n DEFAULT_ORIENTATION_MODEL,\n type LineOrientation,\n LineOrientationClassifier,\n} from './line-orientation.js'\n\nexport interface LineOrientationDetectorParams extends BaseStageParams {\n /** [imageColumn, boxColumn]. */\n inputCols: string[]\n /** Column the corrected image is written to. */\n outputCol: string\n /** Column the per-box orientation labels are written to. */\n orientationCol: string\n model: string\n /** Turn the inverted regions. Off classifies only, leaving the page as-is. */\n correct: boolean\n /**\n * Classify only boxes that are already rotated, as ScaleDP's `onlyRotated`\n * does, and defaults to.\n *\n * This is not just a saved inference per box. The classifier has a real\n * false-positive rate: on an upright invoice it called 1 of 81 upright\n * regions inverted, and turning that region cost about 40 characters of\n * recognition. Restricting it to boxes that are already rotated is where\n * the signal actually is.\n *\n * Set false for pages that may contain upside-down *horizontal* text, which\n * is the case this misses.\n */\n onlyRotated: boolean\n /** Grow each box before cropping, so glyph edges are not clipped. */\n padding: number\n imageType: ImageFormat\n}\n\nexport const LINE_ORIENTATION_DEFAULTS: LineOrientationDetectorParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'image',\n inputCols: ['image', 'boxes'],\n outputCol: 'oriented',\n orientationCol: 'orientations',\n keepInputData: true,\n model: DEFAULT_ORIENTATION_MODEL,\n correct: true,\n onlyRotated: true,\n padding: 2,\n imageType: 'png' as ImageFormat,\n})\n\nfunction boxesOf(source: unknown): Box[] {\n if (typeof source !== 'object' || source === null) return []\n return (source as DetectorOutput | Document).bboxes ?? []\n}\n\nexport class LineOrientationDetector extends Stage<LineOrientationDetectorParams> {\n readonly name = 'LineOrientationDetector'\n\n private classifier: LineOrientationClassifier | null = null\n\n constructor(options: Partial<LineOrientationDetectorParams> = {}) {\n super(\n resolveParams(LINE_ORIENTATION_DEFAULTS, options, {\n inputCols: (value) => {\n if (value.length !== 2) {\n throw new RangeError('inputCols must be [imageColumn, boxColumn]')\n }\n },\n })\n )\n }\n\n override async init(): Promise<void> {\n this.classifier ??= new LineOrientationClassifier(this.params.model)\n }\n\n /** One row in, one row out -- but two columns written, so expand not apply. */\n protected override async expand(_input: unknown, row: Row, ctx: StageContext): Promise<Row[]> {\n const { inputCols, outputCol, orientationCol, correct, onlyRotated, padding } = this.params\n const [imageCol, boxCol] = inputCols as [string, string]\n const image = row[imageCol] as ScaleDpImage | undefined\n\n if (image?.exception) {\n throw new DetectionError(`Upstream stage failed: ${image.exception}`, this.name)\n }\n if (!image || !(image.data instanceof Uint8Array) || image.data.byteLength === 0) {\n throw new DetectionError('Expected an Image with decoded bytes', this.name)\n }\n\n await this.init()\n const classifier = this.classifier as LineOrientationClassifier\n const boxes = boxesOf(row[boxCol])\n\n const bitmap = await decodeImage(image.data)\n try {\n const canvas = createCanvas(bitmap.width, bitmap.height)\n const ctx2d = context2d(canvas)\n ctx2d.drawImage(bitmap, 0, 0)\n\n const orientations: LineOrientation[] = []\n let flipped = 0\n\n for (const box of boxes) {\n ctx.signal?.throwIfAborted()\n\n if (onlyRotated && !isRotated(box)) {\n orientations.push('0_degree')\n continue\n }\n\n const crop = cropBox(bitmap, box, { padding })\n const orientation = await classifier.classify(crop)\n orientations.push(orientation)\n\n if (orientation === '180_degree' && correct) {\n flipRegion(ctx2d, canvas, box)\n flipped++\n }\n }\n\n const corrected =\n flipped > 0\n ? createImage({\n path: image.path,\n resolution: image.resolution,\n data: await encodeImage(canvas, `image/${this.params.imageType}` as never),\n imageType: this.params.imageType,\n width: canvas.width,\n height: canvas.height,\n })\n : // Nothing was inverted, so hand the original through rather\n // than paying a re-encode for an identical image.\n image\n\n return [{ ...row, [outputCol]: corrected, [orientationCol]: orientations }]\n } finally {\n bitmap.close()\n }\n }\n\n protected async apply(): Promise<never> {\n throw new DetectionError('unreachable: expand handles every row', this.name)\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 override async dispose(): Promise<void> {\n await this.classifier?.dispose()\n this.classifier = null\n }\n}\n\n/**\n * Turn one box's region 180 degrees in place.\n *\n * A rectangle maps onto itself under a 180-degree rotation about its own\n * centre, whatever its angle, so the box's coordinates stay valid and\n * downstream stages need no adjustment.\n *\n * The draw is clipped to the box's own rotated outline. Without that a rotated\n * box turns its whole axis-aligned envelope, dragging neighbouring text through\n * the rotation with it -- and the envelope of a skewed box is meaningfully\n * larger than the box.\n */\nfunction flipRegion(ctx: OffscreenCanvasRenderingContext2D, canvas: OffscreenCanvas, box: Box): void {\n const pad = 1\n const x = Math.max(0, Math.min(Math.round(box.x) - pad, canvas.width - 1))\n const y = Math.max(0, Math.min(Math.round(box.y) - pad, canvas.height - 1))\n const width = Math.min(Math.round(box.width) + pad * 2, canvas.width - x)\n const height = Math.min(Math.round(box.height) + pad * 2, canvas.height - y)\n if (width < 1 || height < 1) return\n\n // Drawing a canvas onto itself through a transform reads and writes the same\n // backing store, so take a copy first.\n const region = createCanvas(width, height)\n context2d(region).drawImage(canvas, x, y, width, height, 0, 0, width, height)\n\n const cx = x + width / 2\n const cy = y + height / 2\n\n ctx.save()\n ctx.beginPath()\n if (Math.abs(box.angle) < 3) {\n ctx.rect(x, y, width, height)\n } else {\n const rad = (box.angle * Math.PI) / 180\n const cos = Math.cos(rad)\n const sin = Math.sin(rad)\n const corners: [number, number][] = [\n [-box.width / 2, -box.height / 2],\n [box.width / 2, -box.height / 2],\n [box.width / 2, box.height / 2],\n [-box.width / 2, box.height / 2],\n ]\n corners.forEach(([px, py], index) => {\n const rx = px * cos - py * sin + cx\n const ry = px * sin + py * cos + cy\n if (index === 0) ctx.moveTo(rx, ry)\n else ctx.lineTo(rx, ry)\n })\n ctx.closePath()\n }\n ctx.clip()\n\n ctx.translate(cx, cy)\n ctx.rotate(Math.PI)\n ctx.drawImage(region, -width / 2, -height / 2)\n ctx.restore()\n}\n","/**\n * PaddleOCR language presets.\n *\n * Each preset pairs a detection model, a recognition model and a character\n * dictionary. There is no single model covering every script, so the choice is\n * a real one: pick the pairing that matches the documents being processed.\n *\n * Curated from ppu-paddle-ocr's catalogue -- the full list also carries v3/v4\n * models that v5/v6 supersede.\n */\n\nexport interface OcrPreset {\n /** Key accepted by `PaddleTextDetector`/`PaddleTextRecognizer`. */\n value: string\n /** Human-readable name, suitable for a language picker. */\n label: string\n /** Scripts this preset can read. */\n scripts: readonly string[]\n}\n\nexport const PADDLE_OCR_PRESETS: readonly OcrPreset[] = Object.freeze([\n { value: 'v6-small', label: 'Latin / CJK (default)', scripts: ['Latin', 'Han', 'Hiragana', 'Katakana'] },\n {\n value: 'v6-medium',\n label: 'Latin / CJK (medium, more accurate)',\n scripts: ['Latin', 'Han', 'Hiragana', 'Katakana'],\n },\n { value: 'v6-tiny', label: 'Latin / CJK (tiny, fastest)', scripts: ['Latin', 'Han'] },\n { value: 'v5-latin-mobile', label: 'Latin (French, German, Spanish, ...)', scripts: ['Latin'] },\n {\n value: 'v5-eslav-mobile',\n label: 'Latin + Cyrillic (Russian, Ukrainian, ...)',\n scripts: ['Latin', 'Cyrillic'],\n },\n { value: 'v5-cyrillic-mobile', label: 'Cyrillic only', scripts: ['Cyrillic'] },\n { value: 'v5-devanagari-mobile', label: 'Latin + Hindi (Devanagari)', scripts: ['Latin', 'Devanagari'] },\n { value: 'v5-arabic-mobile', label: 'Latin + Arabic', scripts: ['Latin', 'Arabic'] },\n { value: 'v5-greek-mobile', label: 'Latin + Greek', scripts: ['Latin', 'Greek'] },\n { value: 'v5-korean-mobile', label: 'Latin + Korean', scripts: ['Latin', 'Hangul'] },\n { value: 'v5-thai-mobile', label: 'Latin + Thai', scripts: ['Latin', 'Thai'] },\n { value: 'v5-tamil-mobile', label: 'Latin + Tamil', scripts: ['Latin', 'Tamil'] },\n { value: 'v5-telugu-mobile', label: 'Latin + Telugu', scripts: ['Latin', 'Telugu'] },\n { value: 'v5-en-mobile', label: 'English only (fastest)', scripts: ['Latin'] },\n])\n\nexport const DEFAULT_OCR_PRESET = 'v6-small'\n\nexport function isKnownPreset(value: string): boolean {\n return PADDLE_OCR_PRESETS.some((p) => p.value === value)\n}\n\n/** Presets able to read a script name as reported by OSD script detection. */\nexport function presetsForScript(script: string): OcrPreset[] {\n return PADDLE_OCR_PRESETS.filter((p) => p.scripts.includes(script))\n}\n","/**\n * PaddleOcrService lifecycle: model fetching, caching and per-preset reuse.\n *\n * Models are pulled through our own ModelCache and handed to the service as\n * ArrayBuffers, rather than letting ppu-paddle-ocr fetch them itself. Its\n * browser build re-downloads ~6 MB on every page load, relying only on the HTTP\n * cache; routing through IndexedDB makes a repeat visit instant and offline-safe.\n */\n\nimport { getConfig } from '../core/config.js'\nimport type { ModelSpec } from '../core/model-cache.js'\nimport { ensureModelFiles, evict, isCached } from '../core/model-cache.js'\nimport { loadOrt } from './ort.js'\nimport { DEFAULT_OCR_PRESET } from './presets.js'\n\ntype PpuWeb = typeof import('ppu-paddle-ocr/web')\ntype PaddleOcrService = InstanceType<PpuWeb['PaddleOcrService']>\n\nlet modulePromise: Promise<PpuWeb> | null = null\n\nasync function loadPpu(): Promise<PpuWeb> {\n if (modulePromise) return modulePromise\n modulePromise = (async () => {\n // ORT must be configured before ppu-paddle-ocr creates any session,\n // otherwise its own wasmPaths default wins the \"if unset\" check.\n await loadOrt()\n try {\n return await import('ppu-paddle-ocr/web')\n } catch (cause) {\n throw new Error(\n 'ppu-paddle-ocr is required for PaddleOCR stages. Install it: npm i ppu-paddle-ocr',\n { cause }\n )\n }\n })()\n return modulePromise\n}\n\n/** The three files a preset needs, as a ModelSpec our cache understands. */\nasync function specForPreset(preset: string): Promise<{ spec: ModelSpec; roles: string[] }> {\n const ppu = await loadPpu()\n const urls =\n preset in ppu.MODEL_PRESETS\n ? ppu.MODEL_PRESETS[preset as keyof typeof ppu.MODEL_PRESETS]\n : ppu.DEFAULT_MODEL\n\n const roles = ['detection', 'recognition', 'charactersDictionary'] as const\n // ppu gives absolute URLs; the cache keys on repo-relative paths, so the\n // full URL doubles as the key and `modelHost` is bypassed for these.\n return {\n spec: { repo: `ppu-paddle-ocr/${preset}`, files: roles.map((r) => ({ path: urls[r] })) },\n roles: [...roles],\n }\n}\n\n/**\n * Absolute model URLs bypass `modelHost`: ppu-paddle-ocr publishes its own\n * catalogue and the paths are meaningful only against that host.\n */\nasync function fetchPresetFiles(preset: string): Promise<Record<string, ArrayBuffer>> {\n const { spec, roles } = await specForPreset(preset)\n const files = await ensureModelFiles({\n ...spec,\n files: spec.files.map((f) => ({ ...f })),\n })\n const out: Record<string, ArrayBuffer> = {}\n for (const [i, role] of roles.entries()) {\n const path = spec.files[i]?.path\n if (path && files[path]) out[role] = files[path] as ArrayBuffer\n }\n return out\n}\n\n// Keyed by preset: switching language keeps the previous service alive, so\n// toggling back is instant rather than a re-download plus re-init.\nconst services = new Map<string, Promise<PaddleOcrService>>()\n\nexport async function getPaddleService(preset = DEFAULT_OCR_PRESET): Promise<PaddleOcrService> {\n const existing = services.get(preset)\n if (existing) return existing\n\n const promise = (async () => {\n const ppu = await loadPpu()\n const model = await fetchPresetFiles(preset)\n const service = new ppu.PaddleOcrService({\n model,\n session: {\n executionProviders: [...getConfig().executionProviders] as never,\n graphOptimizationLevel: 'all',\n },\n })\n await service.initialize()\n return service\n })()\n\n // Evict on failure so a transient network error is retried rather than\n // cached as a permanently rejected promise.\n promise.catch(() => services.delete(preset))\n services.set(preset, promise)\n return promise\n}\n\nexport async function isPresetCached(preset: string): Promise<boolean> {\n const { spec } = await specForPreset(preset)\n return isCached(spec)\n}\n\n/** Pre-warm a preset so the first OCR call does not pay the download. */\nexport async function loadPreset(preset: string): Promise<void> {\n await getPaddleService(preset)\n}\n\nexport async function removePreset(preset: string): Promise<void> {\n const { spec } = await specForPreset(preset)\n const service = services.get(preset)\n services.delete(preset)\n await service?.then((s) => s.destroy()).catch(() => undefined)\n await evict(spec)\n}\n\n/** Tear down every cached service. */\nexport async function disposePaddleServices(): Promise<void> {\n const pending = [...services.values()]\n services.clear()\n await Promise.all(pending.map((p) => p.then((s) => s.destroy()).catch(() => undefined)))\n}\n","/**\n * PaddleOCR text detection and recognition -- the default OCR engines.\n *\n * `PaddleTextDetector` mirrors ScaleDP's detector stages (image -> boxes) and\n * `PaddleTextRecognizer` mirrors its OCR stages (image -> Document with text\n * and boxes). Both run PP-OCR models through ppu-paddle-ocr on onnxruntime-web.\n */\n\nimport { OcrError } from '../core/errors.js'\nimport { decodeImage, imageDataToCanvas } from '../core/image.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage } from '../core/pipeline.js'\nimport { boxesToFormattedText, boxesToText } from '../core/text.js'\nimport { type Box, boxFromBBox } from '../schemas/box.js'\nimport { createDetectorOutput, type DetectorOutput } from '../schemas/detector-output.js'\nimport { createDocument, type Document } from '../schemas/document.js'\nimport type { ScaleDpImage } from '../schemas/image.js'\nimport { getPaddleService } from './paddle-service.js'\nimport { DEFAULT_OCR_PRESET, isKnownPreset } from './presets.js'\n\n/** Recognizing per box gives word-level output; per line merges them first. */\nexport type RecognitionStrategy = 'per-box' | 'per-line' | 'cross-line'\n\nexport interface PaddleOcrParams extends BaseStageParams {\n /** Language/script preset. See PADDLE_OCR_PRESETS. */\n preset: string\n /** Drop results below this confidence (0-1). */\n scoreThreshold: number\n}\n\nexport interface PaddleTextDetectorParams extends PaddleOcrParams {}\n\nexport const PADDLE_DETECTOR_DEFAULTS: PaddleTextDetectorParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'image',\n outputCol: 'boxes',\n keepInputData: true,\n preset: DEFAULT_OCR_PRESET,\n scoreThreshold: 0,\n})\n\nfunction validatePreset(value: string): void {\n if (!isKnownPreset(value)) {\n throw new RangeError(`Unknown OCR preset \"${value}\". See PADDLE_OCR_PRESETS for valid values.`)\n }\n}\n\n/** Decode a stage input into something ppu-paddle-ocr accepts. */\nasync function toCanvas(input: unknown): Promise<OffscreenCanvas> {\n if (typeof OffscreenCanvas !== 'undefined' && input instanceof OffscreenCanvas) return input\n if (typeof ImageData !== 'undefined' && input instanceof ImageData) {\n return imageDataToCanvas(input)\n }\n\n const image = input as ScaleDpImage | undefined\n // Check `exception` first. A failed upstream stage returns a well-formed but\n // empty Image, so testing the bytes first would report \"no decoded bytes\" and\n // bury the real cause.\n if (image?.exception) {\n throw new OcrError(`Upstream stage failed: ${image.exception}`, 'toCanvas')\n }\n if (!image || !(image.data instanceof Uint8Array) || image.data.byteLength === 0) {\n throw new OcrError('Expected an Image with decoded bytes', 'toCanvas')\n }\n\n const bitmap = await decodeImage(image.data)\n try {\n const canvas = new OffscreenCanvas(bitmap.width, bitmap.height)\n const ctx = canvas.getContext('2d')\n if (!ctx) throw new OcrError('Failed to acquire a 2D context', 'toCanvas')\n ctx.drawImage(bitmap, 0, 0)\n return canvas\n } finally {\n bitmap.close()\n }\n}\n\n/** Text detection only: image -> word boxes, no recognition. */\nexport class PaddleTextDetector extends Stage<PaddleTextDetectorParams> {\n readonly name = 'PaddleTextDetector'\n\n constructor(options: Partial<PaddleTextDetectorParams> = {}) {\n super(resolveParams(PADDLE_DETECTOR_DEFAULTS, options, { preset: validatePreset }))\n }\n\n override async init(): Promise<void> {\n await getPaddleService(this.params.preset)\n }\n\n protected async apply(input: unknown, row: Row): Promise<DetectorOutput> {\n const service = await getPaddleService(this.params.preset)\n const canvas = await toCanvas(input)\n const { boxes } = await service.detect(canvas as never)\n\n return createDetectorOutput({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'paddle',\n // ppu returns axis-aligned boxes in original image coordinates.\n bboxes: boxes.map((b) => boxFromBBox([b.x, b.y, b.x + b.width, b.y + b.height], { score: 1 })),\n })\n }\n\n protected onError(message: string, row: Row): DetectorOutput {\n return createDetectorOutput({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'paddle',\n exception: message,\n })\n }\n}\n\nexport interface PaddleTextRecognizerParams extends PaddleOcrParams {\n /**\n * How recognized regions are grouped. 'per-box' keeps them as detected,\n * 'per-line' merges them into lines.\n *\n * ScaleDP's docstring calls 'per-box' word-level. That describes the\n * grouping, not the geometry: the boxes are whatever the preset's detector\n * produced, and PaddleOCR's detector is line-level. No strategy can\n * subdivide a region. `TesseractOcr` is the stage that returns words.\n */\n strategy: RecognitionStrategy\n /** Rebuild the original layout with spaces and blank lines. */\n keepFormatting: boolean\n /** Line-grouping tolerance in pixels; 0 derives it from character height. */\n lineTolerance: number\n}\n\nexport const PADDLE_RECOGNIZER_DEFAULTS: PaddleTextRecognizerParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'image',\n outputCol: 'text',\n keepInputData: true,\n preset: DEFAULT_OCR_PRESET,\n scoreThreshold: 0.5,\n strategy: 'per-box' as RecognitionStrategy,\n keepFormatting: false,\n lineTolerance: 0,\n})\n\n/** Full OCR: image -> Document with text and word-level boxes. */\nexport class PaddleTextRecognizer extends Stage<PaddleTextRecognizerParams> {\n readonly name = 'PaddleTextRecognizer'\n\n constructor(options: Partial<PaddleTextRecognizerParams> = {}) {\n super(resolveParams(PADDLE_RECOGNIZER_DEFAULTS, options, { preset: validatePreset }))\n }\n\n override async init(): Promise<void> {\n await getPaddleService(this.params.preset)\n }\n\n protected async apply(input: unknown, row: Row): Promise<Document> {\n const { preset, strategy, scoreThreshold, keepFormatting, lineTolerance } = this.params\n const service = await getPaddleService(preset)\n const canvas = await toCanvas(input)\n\n const result = await service.recognize(canvas as never, {\n flatten: true,\n strategy,\n // ppu caches globally, keyed on pixels. Without this, switching\n // preset returns the *previous* model's result for the same image.\n noCache: true,\n })\n\n const items = 'results' in result ? result.results : []\n const bboxes: Box[] = items\n .filter((item) => item.confidence >= scoreThreshold)\n .map((item) =>\n boxFromBBox(\n [item.box.x, item.box.y, item.box.x + item.box.width, item.box.y + item.box.height],\n { text: item.text, score: item.confidence }\n )\n )\n\n return createDocument({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'ocr',\n text: keepFormatting ? boxesToFormattedText(bboxes, lineTolerance) : boxesToText(bboxes),\n bboxes,\n })\n }\n\n protected onError(message: string, row: Row): Document {\n return createDocument({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'ocr',\n exception: message,\n })\n }\n}\n","/**\n * Writing-script detection via Tesseract's OSD model.\n *\n * Kept separate from recognition on purpose: when the recognition model is\n * wrong for the page the text comes back garbled, and that is precisely when\n * knowing the script is most useful. Running OSD independently means the\n * answer stays trustworthy.\n *\n * Feeds the preset picker -- see `presetsForScript`.\n */\n\nimport { OcrError } from '../core/errors.js'\nimport { decodeImage, imageDataToCanvas, toImageData } from '../core/image.js'\nimport type { ScaleDpImage } from '../schemas/image.js'\nimport { type OcrPreset, presetsForScript } from './presets.js'\n\nexport interface DetectedScript {\n /** Script name as Tesseract reports it, e.g. 'Latin', 'Cyrillic', 'Han'. */\n script: string\n /** Tesseract's own confidence; not a probability. */\n confidence: number\n}\n\ntype TesseractJs = typeof import('tesseract.js')\ntype OsdWorker = Awaited<ReturnType<TesseractJs['createWorker']>>\n\nlet workerPromise: Promise<OsdWorker> | null = null\n\nasync function getOsdWorker(): Promise<OsdWorker> {\n if (workerPromise) return workerPromise\n\n workerPromise = (async () => {\n let mod: TesseractJs\n try {\n mod = await import('tesseract.js')\n } catch (cause) {\n throw new Error('tesseract.js is required for script detection. Install it: npm i tesseract.js', {\n cause,\n })\n }\n // OEM 0 (legacy engine) is the only one that carries OSD data.\n return mod.createWorker('osd', mod.OEM.TESSERACT_ONLY)\n })()\n\n workerPromise.catch(() => {\n workerPromise = null\n })\n return workerPromise\n}\n\n/** Detect the dominant writing script on a page. Returns null when unsure. */\nexport async function detectScript(\n source: ImageBitmap | OffscreenCanvas | ImageData | ScaleDpImage\n): Promise<DetectedScript | null> {\n const worker = await getOsdWorker()\n\n let canvas: OffscreenCanvas\n if (typeof OffscreenCanvas !== 'undefined' && source instanceof OffscreenCanvas) {\n canvas = source\n } else if (typeof ImageData !== 'undefined' && source instanceof ImageData) {\n canvas = imageDataToCanvas(source)\n } else if (typeof ImageBitmap !== 'undefined' && source instanceof ImageBitmap) {\n canvas = imageDataToCanvas(toImageData(source))\n } else {\n const image = source as ScaleDpImage\n if (!(image.data instanceof Uint8Array) || image.data.byteLength === 0) {\n throw new OcrError('Expected an Image with decoded bytes', 'detectScript')\n }\n const bitmap = await decodeImage(image.data)\n try {\n canvas = imageDataToCanvas(toImageData(bitmap))\n } finally {\n bitmap.close()\n }\n }\n\n const blob = await canvas.convertToBlob({ type: 'image/png' })\n const { data } = await worker.detect(blob)\n if (!data?.script) return null\n return { script: data.script, confidence: data.script_confidence ?? 0 }\n}\n\n/** Presets able to read the detected script, best-first. */\nexport async function suggestPresets(\n source: ImageBitmap | OffscreenCanvas | ImageData | ScaleDpImage\n): Promise<OcrPreset[]> {\n const detected = await detectScript(source)\n return detected ? presetsForScript(detected.script) : []\n}\n\n/** Tear down the shared OSD worker. */\nexport async function disposeScriptDetection(): Promise<void> {\n const pending = workerPromise\n workerPromise = null\n await pending?.then((w) => w.terminate()).catch(() => undefined)\n}\n","/**\n * Tesseract OCR, mirroring ScaleDP's `TesseractOcr` stage.\n *\n * Recognition runs on tesseract-wasm; script detection uses tesseract.js's OSD\n * model. The two are deliberately independent -- script detection still works\n * when the recognition model is wrong for the page and garbles the text, which\n * is exactly when you most want to know the script.\n *\n * Both the worker URL and the traineddata location come from `configure()`.\n * The pdftools prototype hardcoded `/tesseract-worker.js` and a raw GitHub URL,\n * neither of which a library can assume.\n */\n\nimport { getConfig } from '../core/config.js'\nimport { OcrError } from '../core/errors.js'\nimport { decodeImage, toImageData } from '../core/image.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage } from '../core/pipeline.js'\nimport { boxesToFormattedText, boxesToText } from '../core/text.js'\nimport { type Box, boxFromBBox } from '../schemas/box.js'\nimport { createDocument, type Document } from '../schemas/document.js'\nimport type { ScaleDpImage } from '../schemas/image.js'\n\n/** tessdata_fast is a good default: far smaller than tessdata, barely less accurate. */\nexport const DEFAULT_TESSDATA_URL = 'https://raw.githubusercontent.com/tesseract-ocr/tessdata_fast/main/'\n\nexport interface TesseractOcrParams extends BaseStageParams {\n /** Language codes, e.g. ['eng'] or ['eng', 'deu']. */\n lang: readonly string[]\n /** Drop words below this confidence (0-1). */\n scoreThreshold: number\n keepFormatting: boolean\n lineTolerance: number\n}\n\nexport const TESSERACT_OCR_DEFAULTS: TesseractOcrParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'image',\n outputCol: 'text',\n keepInputData: true,\n lang: ['eng'] as readonly string[],\n scoreThreshold: 0.5,\n keepFormatting: false,\n lineTolerance: 0,\n})\n\ntype TesseractWasm = typeof import('tesseract-wasm')\ntype OcrClient = InstanceType<TesseractWasm['OCRClient']>\n\nlet clientPromise: Promise<OcrClient> | null = null\nlet loadedLanguage: string | null = null\n\nfunction trainedDataUrl(lang: string): string {\n const base = (getConfig().tesseract.dataUrl ?? DEFAULT_TESSDATA_URL).replace(/\\/?$/, '/')\n return `${base}${lang}.traineddata`\n}\n\n/**\n * The shared Tesseract client, loading the model for `lang` if needed.\n *\n * Exported so the crop-based recognizer reuses this one client rather than\n * standing up a second worker and loading the same traineddata twice.\n */\nexport async function getTesseractClient(lang: string): Promise<OcrClient> {\n if (clientPromise && loadedLanguage === lang) return clientPromise\n\n // A client holds one model; switching language means a fresh one.\n if (clientPromise) {\n const previous = clientPromise\n clientPromise = null\n await previous.then((c) => c.destroy()).catch(() => undefined)\n }\n\n loadedLanguage = lang\n clientPromise = (async () => {\n let mod: TesseractWasm\n try {\n mod = await import('tesseract-wasm')\n } catch (cause) {\n throw new Error(\n 'tesseract-wasm is required for the Tesseract engine. Install it: npm i tesseract-wasm',\n { cause }\n )\n }\n\n const { workerUrl } = getConfig().tesseract\n const client = new mod.OCRClient(workerUrl ? { workerURL: workerUrl } : {})\n const response = await fetch(trainedDataUrl(lang))\n if (!response.ok) {\n throw new OcrError(\n `Failed to fetch ${lang}.traineddata: ${response.status} ${response.statusText}`,\n 'TesseractOcr'\n )\n }\n await client.loadModel(new Uint8Array(await response.arrayBuffer()))\n return client\n })()\n\n clientPromise.catch(() => {\n clientPromise = null\n loadedLanguage = null\n })\n return clientPromise\n}\n\n/** Tear down the shared Tesseract client. */\nexport async function disposeTesseract(): Promise<void> {\n const pending = clientPromise\n clientPromise = null\n loadedLanguage = null\n await pending?.then((c) => c.destroy()).catch(() => undefined)\n}\n\nexport class TesseractOcr extends Stage<TesseractOcrParams> {\n readonly name = 'TesseractOcr'\n\n constructor(options: Partial<TesseractOcrParams> = {}) {\n super(\n resolveParams(TESSERACT_OCR_DEFAULTS, options, {\n lang: (value) => {\n if (value.length === 0) throw new RangeError('lang must not be empty')\n },\n })\n )\n }\n\n /** tesseract-wasm loads one model, so multi-language means a joined code. */\n private get language(): string {\n return this.params.lang.join('+')\n }\n\n override async init(): Promise<void> {\n await getTesseractClient(this.language)\n }\n\n protected async apply(input: unknown, row: Row): Promise<Document> {\n const image = input as ScaleDpImage | undefined\n // Check `exception` first. A failed upstream stage returns a well-formed but\n // empty Image, so testing the bytes first would report \"no decoded bytes\" and\n // bury the real cause.\n if (image?.exception) {\n throw new OcrError(`Upstream stage failed: ${image.exception}`, this.name)\n }\n if (!image || !(image.data instanceof Uint8Array) || image.data.byteLength === 0) {\n throw new OcrError('Expected an Image with decoded bytes', this.name)\n }\n\n const client = await getTesseractClient(this.language)\n const bitmap = await decodeImage(image.data)\n let words: import('tesseract-wasm').TextItem[]\n try {\n await client.loadImage(toImageData(bitmap))\n // getTextBoxes, not getBoundingBoxes: the latter is layout analysis\n // only and returns geometry with no text and no confidence.\n words = await client.getTextBoxes('word')\n } finally {\n bitmap.close()\n }\n\n const { scoreThreshold, keepFormatting, lineTolerance } = this.params\n const bboxes: Box[] = words\n // tesseract-wasm reports confidence on a 0-1 scale, not 0-100.\n .filter((word) => word.confidence >= scoreThreshold && word.text.trim().length > 0)\n .map((word) =>\n boxFromBBox([word.rect.left, word.rect.top, word.rect.right, word.rect.bottom], {\n text: word.text.trim(),\n score: word.confidence,\n })\n )\n\n return createDocument({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'tesseract',\n text: keepFormatting ? boxesToFormattedText(bboxes, lineTolerance) : boxesToText(bboxes),\n bboxes,\n })\n }\n\n protected onError(message: string, row: Row): Document {\n return createDocument({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'tesseract',\n exception: message,\n })\n }\n\n override async dispose(): Promise<void> {\n await disposeTesseract()\n }\n}\n","/**\n * Recognize text inside boxes a detector already found.\n *\n * Port of ScaleDP's `TesseractRecognizer` (a `BaseRecognizer`), which is the\n * half of the OCR story `PaddleTextRecognizer` cannot cover: Paddle detects and\n * recognises in a single pass over the page, so boxes produced by a *separate*\n * detector never reach it -- rotated boxes in particular. This stage takes those\n * boxes, straightens each one, and reads it.\n */\n\nimport { OcrError } from '../core/errors.js'\nimport type { Point } from '../core/geometry.js'\nimport { cropBox, cropGeometry, decodeImage, toImageData } from '../core/image.js'\nimport { BASE_STAGE_DEFAULTS, type BaseStageParams, resolveParams } from '../core/params.js'\nimport { type Row, Stage, type StageContext } from '../core/pipeline.js'\nimport { boxesToFormattedText, boxesToText } from '../core/text.js'\nimport { type Box, boxFromPolygon, isRotated } from '../schemas/box.js'\nimport type { DetectorOutput } from '../schemas/detector-output.js'\nimport { createDocument, type Document } from '../schemas/document.js'\nimport type { ScaleDpImage } from '../schemas/image.js'\nimport { DEFAULT_ORIENTATION_MODEL, LineOrientationClassifier } from './line-orientation.js'\nimport { getTesseractClient } from './tesseract.js'\n\nexport interface TesseractRecognizerParams extends BaseStageParams {\n /** [imageColumn, boxColumn]. */\n inputCols: string[]\n lang: readonly string[]\n /** Resize the page by this factor before cropping. */\n scaleFactor: number\n /** Grow each box before cropping. ScaleDP hardcodes 5. */\n padding: number\n /** Drop words below this confidence (0-1). */\n scoreThreshold: number\n keepFormatting: boolean\n lineTolerance: number\n /**\n * Granularity of the boxes returned.\n *\n * 'region' keeps ScaleDP's behaviour: one box per region the detector\n * found, carrying everything read inside it. Since the detectors here are\n * line-level, so are those boxes.\n *\n * 'word' returns instead the boxes tesseract reports for each word, mapped\n * back through the crop -- rotation included -- into page coordinates. Not\n * in Python ScaleDP, which always returns one box per region.\n */\n boxLevel: 'region' | 'word'\n /** Classify each crop 0/180 degrees and turn the inverted ones. */\n detectLineOrientation: boolean\n /**\n * Recognize only boxes that are rotated or came back inverted.\n *\n * ScaleDP defaults this to true because there the stage refines an OCR pass\n * that already ran. Standalone it is the primary recognizer, and skipping\n * every upright box would return an empty document for the ordinary case,\n * so the default is flipped here. See docs/porting.md.\n */\n onlyRotated: boolean\n oriModel: string\n}\n\nexport const TESSERACT_RECOGNIZER_DEFAULTS: TesseractRecognizerParams = Object.freeze({\n ...BASE_STAGE_DEFAULTS,\n inputCol: 'image',\n inputCols: ['image', 'boxes'],\n outputCol: 'text',\n keepInputData: true,\n lang: ['eng'] as readonly string[],\n scaleFactor: 1,\n padding: 5,\n scoreThreshold: 0.5,\n keepFormatting: false,\n lineTolerance: 0,\n boxLevel: 'region' as 'region' | 'word',\n detectLineOrientation: true,\n onlyRotated: false,\n oriModel: DEFAULT_ORIENTATION_MODEL,\n})\n\nfunction boxesOf(source: unknown): Box[] {\n if (typeof source !== 'object' || source === null) return []\n return (source as DetectorOutput).bboxes ?? []\n}\n\nexport class TesseractRecognizer extends Stage<TesseractRecognizerParams> {\n readonly name = 'TesseractRecognizer'\n\n private orientation: LineOrientationClassifier | null = null\n\n constructor(options: Partial<TesseractRecognizerParams> = {}) {\n super(\n resolveParams(TESSERACT_RECOGNIZER_DEFAULTS, options, {\n inputCols: (value) => {\n if (value.length !== 2) {\n throw new RangeError('inputCols must be [imageColumn, boxColumn]')\n }\n },\n lang: (value) => {\n if (value.length === 0) throw new RangeError('lang must not be empty')\n },\n })\n )\n }\n\n private get language(): string {\n return this.params.lang.join('+')\n }\n\n override async init(): Promise<void> {\n await getTesseractClient(this.language)\n if (this.params.detectLineOrientation) {\n this.orientation ??= new LineOrientationClassifier(this.params.oriModel)\n }\n }\n\n protected async apply(_input: unknown, row: Row, ctx: StageContext): Promise<Document> {\n const {\n inputCols,\n scaleFactor,\n padding,\n scoreThreshold,\n keepFormatting,\n lineTolerance,\n boxLevel,\n detectLineOrientation,\n onlyRotated,\n } = this.params\n const [imageCol, boxCol] = inputCols as [string, string]\n const image = row[imageCol] as ScaleDpImage | undefined\n\n if (image?.exception) {\n throw new OcrError(`Upstream stage failed: ${image.exception}`, this.name)\n }\n if (!image || !(image.data instanceof Uint8Array) || image.data.byteLength === 0) {\n throw new OcrError('Expected an Image with decoded bytes', this.name)\n }\n\n const source = row[boxCol]\n if (source === undefined) {\n throw new OcrError(\n `No boxes in column \"${boxCol}\". This stage reads a detector's output; ` +\n 'run a text detector before it.',\n this.name\n )\n }\n\n await this.init()\n const client = await getTesseractClient(this.language)\n const bitmap = await decodeImage(image.data)\n const recognized: Box[] = []\n\n try {\n for (const box of boxesOf(source)) {\n ctx.signal?.throwIfAborted()\n\n // Straightens rotated boxes rather than taking their envelope,\n // which is what makes a skewed line readable at all. The same\n // geometry maps word boxes back out again.\n const geometry = cropGeometry(box, { scaleFactor, padding })\n let crop = cropBox(bitmap, box, { scaleFactor, padding })\n\n let inverted = false\n if (detectLineOrientation && this.orientation) {\n inverted = (await this.orientation.classify(crop)) === '180_degree'\n if (inverted) crop = rotate180(crop)\n }\n\n // ScaleDP's onlyRotated: an upright, right-way-up box was\n // already handled by whatever pass produced it.\n if (onlyRotated && !isRotated(box) && !inverted) continue\n\n await client.loadImage(toImageData(crop))\n const items = await client.getTextBoxes('word')\n\n const words = items.filter((item) => item.text.trim())\n if (words.length === 0) continue\n\n // tesseract-wasm reports confidence on a 0-1 scale, not 0-100.\n // ScaleDP writes its confidence to `conf` and then filters on\n // `score`, so its threshold silently applies to the detector's\n // score instead; the value goes where it is read here.\n // Averaged over every item tesseract returned, empty ones\n // included, which is what the region mode did before word boxes\n // existed -- so a pipeline that was passing this gate still is.\n const score = items.reduce((sum, item) => sum + item.confidence, 0) / (items.length || 1)\n\n // The gate is the region's score in both modes, so `boxLevel`\n // changes how finely the result is cut up and nothing else. Per\n // word it would also change *what was read*: a word scoring 0.3\n // between two at 0.9 rides out on the region's mean, and would\n // vanish on its own -- so the same page would yield different\n // text depending on the box size asked for. Each word still\n // carries its own confidence for filtering further downstream.\n if (score < scoreThreshold) continue\n\n if (boxLevel === 'word') {\n for (const item of words) {\n recognized.push(\n wordBox(\n item.rect,\n geometry,\n inverted,\n scaleFactor,\n item.text.trim(),\n item.confidence\n )\n )\n }\n continue\n }\n\n recognized.push({ ...box, text: words.map((item) => item.text.trim()).join(' '), score })\n }\n } finally {\n bitmap.close()\n }\n\n // Coordinates came from the scaled page, so bring them back. Word boxes\n // are already in page coordinates -- `wordBox` divides as it maps, so it\n // rounds once rather than twice.\n const bboxes =\n scaleFactor === 1 || boxLevel === 'word'\n ? recognized\n : recognized.map((box) => ({\n ...box,\n x: Math.round(box.x / scaleFactor),\n y: Math.round(box.y / scaleFactor),\n width: Math.round(box.width / scaleFactor),\n height: Math.round(box.height / scaleFactor),\n }))\n\n return createDocument({\n path: String(row[this.params.pathCol] ?? image.path),\n type: 'tesseract-recognizer',\n text: keepFormatting ? boxesToFormattedText(bboxes, lineTolerance) : boxesToText(bboxes),\n bboxes,\n })\n }\n\n protected onError(message: string, row: Row): Document {\n return createDocument({\n path: String(row[this.params.pathCol] ?? 'memory'),\n type: 'tesseract-recognizer',\n exception: message,\n })\n }\n\n override async dispose(): Promise<void> {\n await this.orientation?.dispose()\n this.orientation = null\n }\n}\n\n/**\n * One word's box, in the page's coordinates.\n *\n * tesseract reports the rect in the crop's own space: straightened, padded,\n * scaled, and turned the right way up if the line was upside down. Undo the\n * turn, then push the four corners back through the crop's own transform, so a\n * word inside a skewed line comes back skewed the same way.\n */\nfunction wordBox(\n rect: { left: number; top: number; right: number; bottom: number },\n geometry: ReturnType<typeof cropGeometry>,\n inverted: boolean,\n scaleFactor: number,\n text: string,\n score: number\n): Box {\n const { width, height, map } = geometry\n // rotate180 maps (x, y) to (w - x, h - y), so the rect's corners swap.\n const [left, top, right, bottom] = inverted\n ? [width - rect.right, height - rect.bottom, width - rect.left, height - rect.top]\n : [rect.left, rect.top, rect.right, rect.bottom]\n\n const toPage = (x: number, y: number): Point => {\n const [px, py] = map(x, y)\n return [px / scaleFactor, py / scaleFactor]\n }\n const corners: Point[] = [\n toPage(left, top),\n toPage(right, top),\n toPage(right, bottom),\n toPage(left, bottom),\n ]\n return boxFromPolygon(corners, { text, score })\n}\n\n/** Turn a crop 180 degrees, so an inverted line reads the right way up. */\nfunction rotate180(source: OffscreenCanvas): OffscreenCanvas {\n const out = new OffscreenCanvas(source.width, source.height)\n const ctx = out.getContext('2d')\n if (!ctx) throw new OcrError('Failed to acquire a 2D context', 'TesseractRecognizer')\n ctx.translate(source.width / 2, source.height / 2)\n ctx.rotate(Math.PI)\n ctx.drawImage(source, -source.width / 2, -source.height / 2)\n return out\n}\n"],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ import { _ as resolveNumThreads, h as getConfig } from "./pipeline-DACqGkpN.js";
2
+ //#region src/ocr/ort.ts
3
+ /**
4
+ * onnxruntime-web setup, shared by every ONNX-backed stage.
5
+ *
6
+ * onnxruntime-web is an optional peer dependency, imported lazily so the core
7
+ * pulls in no ML runtime.
8
+ */
9
+ let modulePromise = null;
10
+ /**
11
+ * onnxruntime-web's JS glue and its .wasm binary must come from the same build
12
+ * variant AND the same version -- a mismatch fails at session creation with an
13
+ * opaque error. Deriving the CDN URL from the resolved package version is what
14
+ * keeps them in step; the pdftools prototype hardcoded 1.23.2 in three files
15
+ * while actually running 1.26/1.27.
16
+ */
17
+ function cdnWasmPaths(version) {
18
+ return `https://cdn.jsdelivr.net/npm/onnxruntime-web@${version}/dist/`;
19
+ }
20
+ async function loadOrt() {
21
+ if (modulePromise) return modulePromise;
22
+ modulePromise = (async () => {
23
+ let ort;
24
+ try {
25
+ ort = await import("onnxruntime-web");
26
+ } catch (cause) {
27
+ throw new Error("onnxruntime-web is required for OCR, NER and detection. Install it: npm i onnxruntime-web", { cause });
28
+ }
29
+ const config = getConfig();
30
+ if (config.ortWasmPaths) ort.env.wasm.wasmPaths = config.ortWasmPaths;
31
+ else if (!ort.env.wasm.wasmPaths && ort.env.versions?.web) ort.env.wasm.wasmPaths = cdnWasmPaths(ort.env.versions.web);
32
+ ort.env.wasm.numThreads = resolveNumThreads();
33
+ ort.env.logLevel = "error";
34
+ return ort;
35
+ })();
36
+ return modulePromise;
37
+ }
38
+ /** Reset the cached module. Tests only. */
39
+ function resetOrt() {
40
+ modulePromise = null;
41
+ }
42
+ /** Create an inference session from model bytes. */
43
+ async function createSession(model, options = {}) {
44
+ const ort = await loadOrt();
45
+ const bytes = model instanceof Uint8Array ? model : new Uint8Array(model);
46
+ return ort.InferenceSession.create(bytes, {
47
+ executionProviders: [...options.executionProviders ?? getConfig().executionProviders],
48
+ graphOptimizationLevel: "all",
49
+ ...options.externalData ? { externalData: options.externalData } : {}
50
+ });
51
+ }
52
+ /** True when the browser exposes a usable WebGPU adapter. */
53
+ async function isWebGpuAvailable() {
54
+ const gpu = navigator.gpu;
55
+ if (!gpu) return false;
56
+ try {
57
+ return await gpu.requestAdapter() !== null;
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ /**
63
+ * True when the page is cross-origin isolated, which is what SharedArrayBuffer
64
+ * and therefore multi-threaded WASM require. A library cannot set COOP/COEP for
65
+ * its consumer, so this is reported rather than enforced.
66
+ */
67
+ function isCrossOriginIsolated() {
68
+ return typeof crossOriginIsolated !== "undefined" && crossOriginIsolated === true;
69
+ }
70
+ //#endregion
71
+ export { resetOrt as a, loadOrt as i, isCrossOriginIsolated as n, isWebGpuAvailable as r, createSession as t };
72
+
73
+ //# sourceMappingURL=ort-CXDoPrtw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ort-CXDoPrtw.js","names":[],"sources":["../src/ocr/ort.ts"],"sourcesContent":["/**\n * onnxruntime-web setup, shared by every ONNX-backed stage.\n *\n * onnxruntime-web is an optional peer dependency, imported lazily so the core\n * pulls in no ML runtime.\n */\n\nimport { getConfig, resolveNumThreads } from '../core/config.js'\n\ntype OrtModule = typeof import('onnxruntime-web')\n\nlet modulePromise: Promise<OrtModule> | null = null\n\n/**\n * onnxruntime-web's JS glue and its .wasm binary must come from the same build\n * variant AND the same version -- a mismatch fails at session creation with an\n * opaque error. Deriving the CDN URL from the resolved package version is what\n * keeps them in step; the pdftools prototype hardcoded 1.23.2 in three files\n * while actually running 1.26/1.27.\n */\nfunction cdnWasmPaths(version: string): string {\n return `https://cdn.jsdelivr.net/npm/onnxruntime-web@${version}/dist/`\n}\n\nexport async function loadOrt(): Promise<OrtModule> {\n if (modulePromise) return modulePromise\n\n modulePromise = (async () => {\n let ort: OrtModule\n try {\n ort = await import('onnxruntime-web')\n } catch (cause) {\n throw new Error(\n 'onnxruntime-web is required for OCR, NER and detection. Install it: npm i onnxruntime-web',\n { cause }\n )\n }\n\n const config = getConfig()\n if (config.ortWasmPaths) {\n ort.env.wasm.wasmPaths = config.ortWasmPaths\n } else if (!ort.env.wasm.wasmPaths && ort.env.versions?.web) {\n // Set eagerly, before any session exists, so a bundled library's own\n // \"if not already set\" default never wins.\n ort.env.wasm.wasmPaths = cdnWasmPaths(ort.env.versions.web)\n }\n\n // Threading only engages on a cross-origin-isolated page (COOP/COEP).\n // Without that, ORT silently falls back to its single-threaded build and\n // this value has no effect -- not an error, just a slower run.\n ort.env.wasm.numThreads = resolveNumThreads()\n ort.env.logLevel = 'error'\n return ort\n })()\n\n return modulePromise\n}\n\n/** Reset the cached module. Tests only. */\nexport function resetOrt(): void {\n modulePromise = null\n}\n\nexport interface SessionOptions {\n executionProviders?: readonly string[]\n /** Companion `.onnx_data` files for models with external weights. */\n externalData?: { path: string; data: ArrayBuffer | Uint8Array }[]\n}\n\n/** Create an inference session from model bytes. */\nexport async function createSession(\n model: ArrayBuffer | Uint8Array,\n options: SessionOptions = {}\n): Promise<import('onnxruntime-web').InferenceSession> {\n const ort = await loadOrt()\n const bytes = model instanceof Uint8Array ? model : new Uint8Array(model)\n\n return ort.InferenceSession.create(bytes, {\n executionProviders: [...(options.executionProviders ?? getConfig().executionProviders)] as string[],\n graphOptimizationLevel: 'all',\n ...(options.externalData ? { externalData: options.externalData } : {}),\n })\n}\n\n/** True when the browser exposes a usable WebGPU adapter. */\nexport async function isWebGpuAvailable(): Promise<boolean> {\n const gpu = (navigator as { gpu?: { requestAdapter(): Promise<unknown> } }).gpu\n if (!gpu) return false\n try {\n return (await gpu.requestAdapter()) !== null\n } catch {\n return false\n }\n}\n\n/**\n * True when the page is cross-origin isolated, which is what SharedArrayBuffer\n * and therefore multi-threaded WASM require. A library cannot set COOP/COEP for\n * its consumer, so this is reported rather than enforced.\n */\nexport function isCrossOriginIsolated(): boolean {\n return typeof crossOriginIsolated !== 'undefined' && crossOriginIsolated === true\n}\n"],"mappings":";;;;;;;;AAWA,IAAI,gBAA2C;;;;;;;;AAS/C,SAAS,aAAa,SAAyB;CAC3C,OAAO,gDAAgD,QAAQ;AACnE;AAEA,eAAsB,UAA8B;CAChD,IAAI,eAAe,OAAO;CAE1B,iBAAiB,YAAY;EACzB,IAAI;EACJ,IAAI;GACA,MAAM,MAAM,OAAO;EACvB,SAAS,OAAO;GACZ,MAAM,IAAI,MACN,6FACA,EAAE,MAAM,CACZ;EACJ;EAEA,MAAM,SAAS,UAAU;EACzB,IAAI,OAAO,cACP,IAAI,IAAI,KAAK,YAAY,OAAO;OAC7B,IAAI,CAAC,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,UAAU,KAGpD,IAAI,IAAI,KAAK,YAAY,aAAa,IAAI,IAAI,SAAS,GAAG;EAM9D,IAAI,IAAI,KAAK,aAAa,kBAAkB;EAC5C,IAAI,IAAI,WAAW;EACnB,OAAO;CACX,EAAA,CAAG;CAEH,OAAO;AACX;;AAGA,SAAgB,WAAiB;CAC7B,gBAAgB;AACpB;;AASA,eAAsB,cAClB,OACA,UAA0B,CAAC,GACwB;CACnD,MAAM,MAAM,MAAM,QAAQ;CAC1B,MAAM,QAAQ,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;CAExE,OAAO,IAAI,iBAAiB,OAAO,OAAO;EACtC,oBAAoB,CAAC,GAAI,QAAQ,sBAAsB,UAAU,CAAC,CAAC,kBAAmB;EACtF,wBAAwB;EACxB,GAAI,QAAQ,eAAe,EAAE,cAAc,QAAQ,aAAa,IAAI,CAAC;CACzE,CAAC;AACL;;AAGA,eAAsB,oBAAsC;CACxD,MAAM,MAAO,UAA+D;CAC5E,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI;EACA,OAAQ,MAAM,IAAI,eAAe,MAAO;CAC5C,QAAQ;EACJ,OAAO;CACX;AACJ;;;;;;AAOA,SAAgB,wBAAiC;CAC7C,OAAO,OAAO,wBAAwB,eAAe,wBAAwB;AACjF"}
@@ -0,0 +1,37 @@
1
+ //#region src/core/params.ts
2
+ const BASE_STAGE_DEFAULTS = Object.freeze({
3
+ inputCol: "content",
4
+ outputCol: "output",
5
+ pathCol: "path",
6
+ pageCol: "page",
7
+ keepInputData: false,
8
+ propagateError: false
9
+ });
10
+ /**
11
+ * Merge user options over defaults and run validators.
12
+ *
13
+ * `undefined` values are ignored so `{ scoreThreshold: undefined }` falls back
14
+ * to the default rather than erasing it -- callers spreading optional config
15
+ * would otherwise silently lose defaults.
16
+ */
17
+ function resolveParams(defaults, options = {}, validators = {}) {
18
+ const resolved = { ...defaults };
19
+ for (const key of Object.keys(options)) {
20
+ const value = options[key];
21
+ if (value !== void 0) resolved[key] = value;
22
+ }
23
+ for (const key of Object.keys(validators)) validators[key]?.(resolved[key], resolved);
24
+ return resolved;
25
+ }
26
+ /** Throw unless `value` lies within [min, max]. */
27
+ function assertInRange(name, value, min, max) {
28
+ if (!Number.isFinite(value) || value < min || value > max) throw new RangeError(`${name} must be between ${min} and ${max}, received ${value}`);
29
+ }
30
+ /** Throw unless `value` is a positive integer. */
31
+ function assertPositiveInt(name, value) {
32
+ if (!Number.isInteger(value) || value <= 0) throw new RangeError(`${name} must be a positive integer, received ${value}`);
33
+ }
34
+ //#endregion
35
+ export { resolveParams as i, assertInRange as n, assertPositiveInt as r, BASE_STAGE_DEFAULTS as t };
36
+
37
+ //# sourceMappingURL=params-DapwK9Ns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"params-DapwK9Ns.js","names":[],"sources":["../src/core/params.ts"],"sourcesContent":["/**\n * The TS analogue of `scaledp/params.py`.\n *\n * Python builds params from a frozen `defaultParams` map plus a coercion pass\n * (`HasDefaultEnum._set`) that unwraps enums, normalises `lang`, and runs any\n * `validate<Param>` hook. TypeScript needs neither a metaclass nor Param\n * objects -- an options object merged over a frozen default, with optional\n * per-key validators, covers the same ground with real types.\n */\n\nexport type Validator<T> = { [K in keyof T]?: (value: T[K], all: T) => void }\n\n/** Params every stage accepts, mirroring the Python mixins. */\nexport interface BaseStageParams {\n /** Row field this stage reads. */\n inputCol: string\n /** Row field this stage writes. */\n outputCol: string\n /** Row field holding the source path; used to populate output `path`. */\n pathCol: string\n /** Row field holding the page index for multi-page inputs. */\n pageCol: string\n /** Keep `inputCol` in the output rows instead of dropping it. */\n keepInputData: boolean\n /** Throw on failure instead of recording it in the output's `exception`. */\n propagateError: boolean\n}\n\nexport const BASE_STAGE_DEFAULTS: BaseStageParams = Object.freeze({\n inputCol: 'content',\n outputCol: 'output',\n pathCol: 'path',\n pageCol: 'page',\n keepInputData: false,\n propagateError: false,\n})\n\n/**\n * Merge user options over defaults and run validators.\n *\n * `undefined` values are ignored so `{ scoreThreshold: undefined }` falls back\n * to the default rather than erasing it -- callers spreading optional config\n * would otherwise silently lose defaults.\n */\nexport function resolveParams<T extends object>(\n defaults: Readonly<T>,\n options: Partial<T> = {},\n validators: Validator<T> = {}\n): T {\n const resolved = { ...defaults } as T\n for (const key of Object.keys(options) as (keyof T)[]) {\n const value = options[key]\n if (value !== undefined) resolved[key] = value as T[keyof T]\n }\n for (const key of Object.keys(validators) as (keyof T)[]) {\n validators[key]?.(resolved[key], resolved)\n }\n return resolved\n}\n\n/** Throw unless `value` lies within [min, max]. */\nexport function assertInRange(name: string, value: number, min: number, max: number): void {\n if (!Number.isFinite(value) || value < min || value > max) {\n throw new RangeError(`${name} must be between ${min} and ${max}, received ${value}`)\n }\n}\n\n/** Throw unless `value` is a positive integer. */\nexport function assertPositiveInt(name: string, value: number): void {\n if (!Number.isInteger(value) || value <= 0) {\n throw new RangeError(`${name} must be a positive integer, received ${value}`)\n }\n}\n"],"mappings":";AA4BA,MAAa,sBAAuC,OAAO,OAAO;CAC9D,UAAU;CACV,WAAW;CACX,SAAS;CACT,SAAS;CACT,eAAe;CACf,gBAAgB;AACpB,CAAC;;;;;;;;AASD,SAAgB,cACZ,UACA,UAAsB,CAAC,GACvB,aAA2B,CAAC,GAC3B;CACD,MAAM,WAAW,EAAE,GAAG,SAAS;CAC/B,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,GAAkB;EACnD,MAAM,QAAQ,QAAQ;EACtB,IAAI,UAAU,KAAA,GAAW,SAAS,OAAO;CAC7C;CACA,KAAK,MAAM,OAAO,OAAO,KAAK,UAAU,GACpC,WAAW,IAAI,GAAG,SAAS,MAAM,QAAQ;CAE7C,OAAO;AACX;;AAGA,SAAgB,cAAc,MAAc,OAAe,KAAa,KAAmB;CACvF,IAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,OAAO,QAAQ,KAClD,MAAM,IAAI,WAAW,GAAG,KAAK,mBAAmB,IAAI,OAAO,IAAI,aAAa,OAAO;AAE3F;;AAGA,SAAgB,kBAAkB,MAAc,OAAqB;CACjE,IAAI,CAAC,OAAO,UAAU,KAAK,KAAK,SAAS,GACrC,MAAM,IAAI,WAAW,GAAG,KAAK,wCAAwC,OAAO;AAEpF"}
@@ -0,0 +1,123 @@
1
+ import { l as Stage, m as BaseStageParams, s as Row, u as StageContext } from "../pipeline-DeLO-OCE.js";
2
+ import { n as Box } from "../box-DAfzwfhA.js";
3
+ import { t as Document } from "../document-B8I61TiY.js";
4
+ import { n as ScaleDpImage, t as ImageFormat } from "../image-Dc5TSg46.js";
5
+ //#region src/pdf/extract-text.d.ts
6
+ /** A box plus the reading direction, which splitting needs but ScaleDP's Box lacks. */
7
+ interface TextBox extends Box {
8
+ /** Unit vector, in viewport space, along which reading progresses. */
9
+ readDirX: number;
10
+ readDirY: number;
11
+ /** pdf.js font identifier, e.g. 'g_d0_f1'. */
12
+ fontName: string;
13
+ }
14
+ /** Confidence assigned to text read from a PDF's own text layer. */
15
+ declare const TEXT_LAYER_SCORE = 0.99;
16
+ interface TextItemLike {
17
+ str: string;
18
+ transform: number[];
19
+ width: number;
20
+ height: number;
21
+ fontName?: string;
22
+ }
23
+ interface ViewportLike {
24
+ convertToViewportPoint(x: number, y: number): number[];
25
+ }
26
+ declare function isTextItem(item: unknown): item is TextItemLike;
27
+ /** Convert one pdf.js text item into a viewport-space box. */
28
+ declare function textItemToBox(item: TextItemLike, viewport: ViewportLike): TextBox | null;
29
+ /** Extract every text item on a page as a viewport-space box. */
30
+ declare function extractTextBoxes(page: {
31
+ getTextContent(): Promise<{
32
+ items: unknown[];
33
+ }>;
34
+ }, viewport: ViewportLike): Promise<TextBox[]>;
35
+ //#endregion
36
+ //#region src/pdf/pdf-to-document.d.ts
37
+ interface PdfToDocumentParams extends BaseStageParams {
38
+ /** Pixel space the boxes are expressed in; match PdfToImage to align them. */
39
+ resolution: number;
40
+ pageLimit: number;
41
+ /** Split pdf.js line runs into word boxes. Off yields run-level boxes. */
42
+ splitWords: boolean;
43
+ }
44
+ declare const PDF_TO_DOCUMENT_DEFAULTS: PdfToDocumentParams;
45
+ declare class PdfToDocument extends Stage<PdfToDocumentParams> {
46
+ readonly name = "PdfToDocument";
47
+ constructor(options?: Partial<PdfToDocumentParams>);
48
+ protected expand(input: unknown, row: Row, ctx: StageContext): Promise<Row[]>;
49
+ protected apply(): Promise<never>;
50
+ protected onError(message: string, row: Row): Document;
51
+ }
52
+ /** True when a page's text layer is substantive enough to skip OCR. */
53
+ declare function hasUsableTextLayer(document: Document, minimumBoxes?: number): boolean;
54
+ //#endregion
55
+ //#region src/pdf/pdf-to-image.d.ts
56
+ /** PDF user space is defined in points; 72 of them make an inch. */
57
+ declare const POINTS_PER_INCH = 72;
58
+ interface PdfToImageParams extends BaseStageParams {
59
+ /** Render DPI. 300 matches ScaleDP's default and suits OCR. */
60
+ resolution: number;
61
+ /** Maximum pages to render; 0 renders all of them. */
62
+ pageLimit: number;
63
+ imageType: ImageFormat;
64
+ }
65
+ declare const PDF_TO_IMAGE_DEFAULTS: PdfToImageParams;
66
+ declare class PdfToImage extends Stage<PdfToImageParams> {
67
+ readonly name = "PdfToImage";
68
+ constructor(options?: Partial<PdfToImageParams>);
69
+ /** One input PDF becomes N rows, each carrying its page index. */
70
+ protected expand(input: unknown, row: Row, ctx: StageContext): Promise<Row[]>;
71
+ protected apply(): Promise<never>;
72
+ protected onError(message: string, row: Row): ScaleDpImage;
73
+ }
74
+ /** Rasterise a single 1-based page to encoded image bytes. */
75
+ declare function renderPage(document: Awaited<ReturnType<typeof import('pdfjs-dist').getDocument>['promise']>, pageNumber: number, opts: {
76
+ resolution: number;
77
+ imageType: ImageFormat;
78
+ path: string;
79
+ }): Promise<ScaleDpImage>;
80
+ //#endregion
81
+ //#region src/pdf/pdfjs.d.ts
82
+ /**
83
+ * Lazy pdf.js loader.
84
+ *
85
+ * pdfjs-dist is an optional peer dependency, so it is imported only when a PDF
86
+ * stage actually runs. Every asset path comes from `configure()` -- unlike the
87
+ * pdftools prototype, which hardcoded `/pdf.worker.min.mjs`, a path only its
88
+ * own Next app could serve.
89
+ */
90
+ type PdfjsModule = typeof import('pdfjs-dist');
91
+ declare function loadPdfjs(): Promise<PdfjsModule>;
92
+ /** Reset the cached module. Tests only. */
93
+ declare function resetPdfjs(): void;
94
+ /** Document-level options assembled from the global config. */
95
+ declare function documentOptions(data: Uint8Array): Record<string, unknown>;
96
+ //#endregion
97
+ //#region src/pdf/split-words.d.ts
98
+ /**
99
+ * Rebuild a CSS font string from a pdf.js font name.
100
+ *
101
+ * Names look like `AAAAAA+Helvetica-BoldOblique`: a six-letter subset prefix,
102
+ * then the real family and style suffixes.
103
+ */
104
+ declare function cssFontFromPdfName(fontName: string, size: number): string;
105
+ /**
106
+ * Relative advance width per character, used when no canvas is available.
107
+ * Buckets rather than real metrics -- enough to keep proportions sane.
108
+ */
109
+ declare function relativeCharWidth(char: string): number;
110
+ /**
111
+ * Split one run into word boxes.
112
+ *
113
+ * Returns the run itself when it holds a single word, so the common case costs
114
+ * nothing.
115
+ */
116
+ declare function splitRunIntoWords(run: TextBox): Box[];
117
+ /** Split every run on a page into word boxes. */
118
+ declare function splitRunsIntoWords(runs: readonly TextBox[]): Box[];
119
+ /** Reset the cached measurement canvas. Tests only. */
120
+ declare function resetMeasurementContext(): void;
121
+ //#endregion
122
+ export { PDF_TO_DOCUMENT_DEFAULTS, PDF_TO_IMAGE_DEFAULTS, POINTS_PER_INCH, PdfToDocument, type PdfToDocumentParams, PdfToImage, type PdfToImageParams, TEXT_LAYER_SCORE, type TextBox, cssFontFromPdfName, documentOptions, extractTextBoxes, hasUsableTextLayer, isTextItem, loadPdfjs, relativeCharWidth, renderPage, resetMeasurementContext, resetPdfjs, splitRunIntoWords, splitRunsIntoWords, textItemToBox };
123
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,2 @@
1
+ import { _ as extractTextBoxes, a as relativeCharWidth, c as splitRunsIntoWords, d as PdfToImage, f as renderPage, g as TEXT_LAYER_SCORE, h as resetPdfjs, i as cssFontFromPdfName, l as PDF_TO_IMAGE_DEFAULTS, m as loadPdfjs, n as PdfToDocument, o as resetMeasurementContext, p as documentOptions, r as hasUsableTextLayer, s as splitRunIntoWords, t as PDF_TO_DOCUMENT_DEFAULTS, u as POINTS_PER_INCH, v as isTextItem, y as textItemToBox } from "../pdf-BQl0dneD.js";
2
+ export { PDF_TO_DOCUMENT_DEFAULTS, PDF_TO_IMAGE_DEFAULTS, POINTS_PER_INCH, PdfToDocument, PdfToImage, TEXT_LAYER_SCORE, cssFontFromPdfName, documentOptions, extractTextBoxes, hasUsableTextLayer, isTextItem, loadPdfjs, relativeCharWidth, renderPage, resetMeasurementContext, resetPdfjs, splitRunIntoWords, splitRunsIntoWords, textItemToBox };