@stabrise/scaledp 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +3 -2
- package/dist/{data-to-image-DoZ4jQ3R.js → data-to-image-CRvh92wt.js} +2 -2
- package/dist/{data-to-image-DoZ4jQ3R.js.map → data-to-image-CRvh92wt.js.map} +1 -1
- package/dist/detect/index.js +1 -1
- package/dist/{detect-q8AI_Jdj.js → detect-DpTc5Wtc.js} +3 -3
- package/dist/{detect-q8AI_Jdj.js.map → detect-DpTc5Wtc.js.map} +1 -1
- package/dist/{image-CAH2rLv9.js → image-DRBsbv7G.js} +16 -2
- package/dist/{image-CAH2rLv9.js.map → image-DRBsbv7G.js.map} +1 -1
- package/dist/{image-draw-boxes-De0QbFv9.js → image-draw-boxes-DuR8eBeg.js} +2 -2
- package/dist/{image-draw-boxes-De0QbFv9.js.map → image-draw-boxes-DuR8eBeg.js.map} +1 -1
- package/dist/index.d.ts +8 -1
- package/dist/index.js +4 -4
- package/dist/ner/index.js +1 -1
- package/dist/{ner-SsZLZ6ed.js → ner-BB3vGNxO.js} +2 -2
- package/dist/{ner-SsZLZ6ed.js.map → ner-BB3vGNxO.js.map} +1 -1
- package/dist/ocr/index.d.ts +100 -4
- package/dist/ocr/index.js +3 -3
- package/dist/{ocr-OHX2WM3e.js → ocr-Dkkbk0Wl.js} +282 -25
- package/dist/ocr-Dkkbk0Wl.js.map +1 -0
- package/dist/{ort-CXDoPrtw.js → ort-DZEG14nY.js} +25 -4
- package/dist/ort-DZEG14nY.js.map +1 -0
- package/dist/pdf/index.js +1 -1
- package/dist/{pdf-BQl0dneD.js → pdf-qbdnOnqZ.js} +3 -3
- package/dist/{pdf-BQl0dneD.js.map → pdf-qbdnOnqZ.js.map} +1 -1
- package/dist/registry/index.js +97 -8
- package/dist/registry/index.js.map +1 -1
- package/package.json +2 -1
- package/dist/ocr-OHX2WM3e.js.map +0 -1
- package/dist/ort-CXDoPrtw.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { h as getConfig, i as Stage, s as DetectionError, u as OcrError } from "./pipeline-DACqGkpN.js";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as minAreaRect, C as isRotated, M as polygonPerimeter, O as boxPoints, T as mergeOverlappingBoxes, a as cropBox, c as encodeImage, f as resize, h as toNchwFloat32, i as createCanvas, j as polygonArea, l as imageDataToCanvas, m as toImageData, n as IMAGENET_STD, o as cropGeometry, p as rotate180, r as context2d, s as decodeImage, t as IMAGENET_MEAN, u as letterbox, v as boxFromBBox, y as boxFromPolygon } from "./image-DRBsbv7G.js";
|
|
3
3
|
import { a as isCached, n as ensureModelFiles, r as evict } from "./model-cache-BEaqqRZ9.js";
|
|
4
4
|
import { i as resolveParams, t as BASE_STAGE_DEFAULTS } from "./params-DapwK9Ns.js";
|
|
5
5
|
import { n as boxesToText, t as boxesToFormattedText } from "./text-ahMLpxN9.js";
|
|
6
6
|
import { t as createDetectorOutput } from "./detector-output-lyF1Mqb8.js";
|
|
7
7
|
import { n as createDocument, t as createImage } from "./image-DoZDJkcR.js";
|
|
8
|
-
import { i as loadOrt, t as createSession } from "./ort-
|
|
8
|
+
import { i as loadOrt, t as createSession } from "./ort-DZEG14nY.js";
|
|
9
9
|
//#region src/ocr/db-postprocess.ts
|
|
10
10
|
/**
|
|
11
11
|
* Differentiable Binarization post-processing.
|
|
@@ -484,7 +484,7 @@ const LINE_ORIENTATION_DEFAULTS = Object.freeze({
|
|
|
484
484
|
padding: 2,
|
|
485
485
|
imageType: "png"
|
|
486
486
|
});
|
|
487
|
-
function boxesOf$
|
|
487
|
+
function boxesOf$2(source) {
|
|
488
488
|
if (typeof source !== "object" || source === null) return [];
|
|
489
489
|
return source.bboxes ?? [];
|
|
490
490
|
}
|
|
@@ -508,7 +508,7 @@ var LineOrientationDetector = class extends Stage {
|
|
|
508
508
|
if (!image || !(image.data instanceof Uint8Array) || image.data.byteLength === 0) throw new DetectionError("Expected an Image with decoded bytes", this.name);
|
|
509
509
|
await this.init();
|
|
510
510
|
const classifier = this.classifier;
|
|
511
|
-
const boxes = boxesOf$
|
|
511
|
+
const boxes = boxesOf$2(row[boxCol]);
|
|
512
512
|
const bitmap = await decodeImage(image.data);
|
|
513
513
|
try {
|
|
514
514
|
const canvas = createCanvas(bitmap.width, bitmap.height);
|
|
@@ -698,6 +698,10 @@ const DEFAULT_OCR_PRESET = "v6-small";
|
|
|
698
698
|
function isKnownPreset(value) {
|
|
699
699
|
return PADDLE_OCR_PRESETS.some((p) => p.value === value);
|
|
700
700
|
}
|
|
701
|
+
/** Shared by every Paddle stage, so an unknown preset fails at construction. */
|
|
702
|
+
function validatePreset(value) {
|
|
703
|
+
if (!isKnownPreset(value)) throw new RangeError(`Unknown OCR preset "${value}". See PADDLE_OCR_PRESETS for valid values.`);
|
|
704
|
+
}
|
|
701
705
|
/** Presets able to read a script name as reported by OSD script detection. */
|
|
702
706
|
function presetsForScript(script) {
|
|
703
707
|
return PADDLE_OCR_PRESETS.filter((p) => p.scripts.includes(script));
|
|
@@ -711,6 +715,13 @@ function presetsForScript(script) {
|
|
|
711
715
|
* ArrayBuffers, rather than letting ppu-paddle-ocr fetch them itself. Its
|
|
712
716
|
* browser build re-downloads ~6 MB on every page load, relying only on the HTTP
|
|
713
717
|
* cache; routing through IndexedDB makes a repeat visit instant and offline-safe.
|
|
718
|
+
*
|
|
719
|
+
* Two entry points, because the two halves of a preset are separable:
|
|
720
|
+
* `getPaddleService` builds the full detect-then-read service, while
|
|
721
|
+
* `getPaddleRecognizer` loads only the recognition model and its dictionary --
|
|
722
|
+
* what `PaddleRecognizer` needs when some other detector already found the
|
|
723
|
+
* boxes. Both key their cache on the same repo, so a pipeline using both pays
|
|
724
|
+
* for each file once.
|
|
714
725
|
*/
|
|
715
726
|
let modulePromise = null;
|
|
716
727
|
async function loadPpu() {
|
|
@@ -780,6 +791,77 @@ async function getPaddleService(preset = DEFAULT_OCR_PRESET) {
|
|
|
780
791
|
services.set(preset, promise);
|
|
781
792
|
return promise;
|
|
782
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* The recognition ONNX session and its character dictionary, per preset.
|
|
796
|
+
*
|
|
797
|
+
* Deliberately separate from `services`: a recognizer running behind someone
|
|
798
|
+
* else's detector never needs the detection model, and skipping it saves a
|
|
799
|
+
* download of a few MB. The cache `repo` is the same either way, so the two
|
|
800
|
+
* files are shared with the full service rather than stored twice.
|
|
801
|
+
*/
|
|
802
|
+
const recognitionAssets = /* @__PURE__ */ new Map();
|
|
803
|
+
/**
|
|
804
|
+
* Split a PaddleOCR dictionary into an ordered array, one entry per line.
|
|
805
|
+
*
|
|
806
|
+
* ppu exports `parseDictionary` from a module its `exports` map does not
|
|
807
|
+
* publish, so this reproduces it -- and it is the whole of it. Blank entries
|
|
808
|
+
* are preserved: the index is the class id the model emits, so dropping one
|
|
809
|
+
* would shift every character after it.
|
|
810
|
+
*/
|
|
811
|
+
function parseDictionary(source) {
|
|
812
|
+
return new TextDecoder("utf-8").decode(source).split(/\r?\n/);
|
|
813
|
+
}
|
|
814
|
+
async function loadRecognitionAssets(preset) {
|
|
815
|
+
const existing = recognitionAssets.get(preset);
|
|
816
|
+
if (existing) return existing;
|
|
817
|
+
const promise = (async () => {
|
|
818
|
+
await loadPpu();
|
|
819
|
+
const { spec, roles } = await specForPreset(preset);
|
|
820
|
+
const wanted = ["recognition", "charactersDictionary"].map((role) => spec.files[roles.indexOf(role)]?.path ?? "");
|
|
821
|
+
const files = await ensureModelFiles({
|
|
822
|
+
...spec,
|
|
823
|
+
files: wanted.map((path) => ({ path }))
|
|
824
|
+
});
|
|
825
|
+
const model = files[wanted[0]];
|
|
826
|
+
const dict = files[wanted[1]];
|
|
827
|
+
if (!model || !dict) throw new Error(`PaddleOCR preset "${preset}" is missing its recognition model or dictionary.`);
|
|
828
|
+
const dictionary = parseDictionary(dict);
|
|
829
|
+
if (dictionary.length === 0) throw new Error(`PaddleOCR preset "${preset}" has an empty character dictionary.`);
|
|
830
|
+
return {
|
|
831
|
+
session: await createSession(model, { fallbackToWasm: true }),
|
|
832
|
+
dictionary
|
|
833
|
+
};
|
|
834
|
+
})();
|
|
835
|
+
promise.catch(() => recognitionAssets.delete(preset));
|
|
836
|
+
recognitionAssets.set(preset, promise);
|
|
837
|
+
return promise;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Recognition only: read the regions you hand it, no detection.
|
|
841
|
+
*
|
|
842
|
+
* `minimumConfidence` is pinned to 0 and `maxCropSourceSideLength` is set past
|
|
843
|
+
* any canvas we pass. ppu would otherwise silently drop low-scoring results --
|
|
844
|
+
* breaking the caller's box-to-result mapping, since `run` also sorts what it
|
|
845
|
+
* returns into reading order -- and downscale a tall batch of stacked crops.
|
|
846
|
+
* Filtering is the calling stage's job, where the threshold is a parameter.
|
|
847
|
+
*/
|
|
848
|
+
async function getPaddleRecognizer(preset = DEFAULT_OCR_PRESET, options = {}) {
|
|
849
|
+
const ppu = await loadPpu();
|
|
850
|
+
const { session, dictionary } = await loadRecognitionAssets(preset);
|
|
851
|
+
return new ppu.RecognitionService(session, {
|
|
852
|
+
charactersDictionary: dictionary,
|
|
853
|
+
minimumConfidence: 0,
|
|
854
|
+
maxCropSourceSideLength: Number.MAX_SAFE_INTEGER,
|
|
855
|
+
...options.recBatchSize === void 0 ? {} : { recBatchSize: options.recBatchSize },
|
|
856
|
+
...options.spaceRecovery === void 0 ? {} : { spaceRecovery: options.spaceRecovery }
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
async function releaseRecognition(preset) {
|
|
860
|
+
const pending = recognitionAssets.get(preset);
|
|
861
|
+
if (!pending) return;
|
|
862
|
+
recognitionAssets.delete(preset);
|
|
863
|
+
await pending.then(({ session }) => session.release()).catch(() => void 0);
|
|
864
|
+
}
|
|
783
865
|
async function isPresetCached(preset) {
|
|
784
866
|
const { spec } = await specForPreset(preset);
|
|
785
867
|
return isCached(spec);
|
|
@@ -793,13 +875,15 @@ async function removePreset(preset) {
|
|
|
793
875
|
const service = services.get(preset);
|
|
794
876
|
services.delete(preset);
|
|
795
877
|
await service?.then((s) => s.destroy()).catch(() => void 0);
|
|
878
|
+
await releaseRecognition(preset);
|
|
796
879
|
await evict(spec);
|
|
797
880
|
}
|
|
798
|
-
/** Tear down every cached service. */
|
|
881
|
+
/** Tear down every cached service, recognition-only sessions included. */
|
|
799
882
|
async function disposePaddleServices() {
|
|
800
883
|
const pending = [...services.values()];
|
|
801
884
|
services.clear();
|
|
802
|
-
|
|
885
|
+
const recognition = [...recognitionAssets.keys()];
|
|
886
|
+
await Promise.all([...pending.map((p) => p.then((s) => s.destroy()).catch(() => void 0)), ...recognition.map((preset) => releaseRecognition(preset))]);
|
|
803
887
|
}
|
|
804
888
|
//#endregion
|
|
805
889
|
//#region src/ocr/paddle.ts
|
|
@@ -810,7 +894,7 @@ async function disposePaddleServices() {
|
|
|
810
894
|
* `PaddleTextRecognizer` mirrors its OCR stages (image -> Document with text
|
|
811
895
|
* and boxes). Both run PP-OCR models through ppu-paddle-ocr on onnxruntime-web.
|
|
812
896
|
*/
|
|
813
|
-
const
|
|
897
|
+
const PADDLE_TEXT_DETECTOR_DEFAULTS = Object.freeze({
|
|
814
898
|
...BASE_STAGE_DEFAULTS,
|
|
815
899
|
inputCol: "image",
|
|
816
900
|
outputCol: "boxes",
|
|
@@ -818,9 +902,6 @@ const PADDLE_DETECTOR_DEFAULTS = Object.freeze({
|
|
|
818
902
|
preset: DEFAULT_OCR_PRESET,
|
|
819
903
|
scoreThreshold: 0
|
|
820
904
|
});
|
|
821
|
-
function validatePreset(value) {
|
|
822
|
-
if (!isKnownPreset(value)) throw new RangeError(`Unknown OCR preset "${value}". See PADDLE_OCR_PRESETS for valid values.`);
|
|
823
|
-
}
|
|
824
905
|
/** Decode a stage input into something ppu-paddle-ocr accepts. */
|
|
825
906
|
async function toCanvas(input) {
|
|
826
907
|
if (typeof OffscreenCanvas !== "undefined" && input instanceof OffscreenCanvas) return input;
|
|
@@ -843,7 +924,7 @@ async function toCanvas(input) {
|
|
|
843
924
|
var PaddleTextDetector = class extends Stage {
|
|
844
925
|
name = "PaddleTextDetector";
|
|
845
926
|
constructor(options = {}) {
|
|
846
|
-
super(resolveParams(
|
|
927
|
+
super(resolveParams(PADDLE_TEXT_DETECTOR_DEFAULTS, options, { preset: validatePreset }));
|
|
847
928
|
}
|
|
848
929
|
async init() {
|
|
849
930
|
await getPaddleService(this.params.preset);
|
|
@@ -871,7 +952,7 @@ var PaddleTextDetector = class extends Stage {
|
|
|
871
952
|
});
|
|
872
953
|
}
|
|
873
954
|
};
|
|
874
|
-
const
|
|
955
|
+
const PADDLE_TEXT_RECOGNIZER_DEFAULTS = Object.freeze({
|
|
875
956
|
...BASE_STAGE_DEFAULTS,
|
|
876
957
|
inputCol: "image",
|
|
877
958
|
outputCol: "text",
|
|
@@ -886,7 +967,7 @@ const PADDLE_RECOGNIZER_DEFAULTS = Object.freeze({
|
|
|
886
967
|
var PaddleTextRecognizer = class extends Stage {
|
|
887
968
|
name = "PaddleTextRecognizer";
|
|
888
969
|
constructor(options = {}) {
|
|
889
|
-
super(resolveParams(
|
|
970
|
+
super(resolveParams(PADDLE_TEXT_RECOGNIZER_DEFAULTS, options, { preset: validatePreset }));
|
|
890
971
|
}
|
|
891
972
|
async init() {
|
|
892
973
|
await getPaddleService(this.params.preset);
|
|
@@ -925,6 +1006,192 @@ var PaddleTextRecognizer = class extends Stage {
|
|
|
925
1006
|
}
|
|
926
1007
|
};
|
|
927
1008
|
//#endregion
|
|
1009
|
+
//#region src/ocr/paddle-recognizer.ts
|
|
1010
|
+
/**
|
|
1011
|
+
* Read the boxes a detector already found, with a PaddleOCR preset.
|
|
1012
|
+
*
|
|
1013
|
+
* The Paddle counterpart to `TesseractRecognizer`, and for the same reason:
|
|
1014
|
+
* `PaddleTextRecognizer` detects and recognises in a single pass over the page,
|
|
1015
|
+
* so boxes produced by a *separate* detector never reach it. This stage takes
|
|
1016
|
+
* those boxes, straightens each one, and reads it -- which is what lets DBNet or
|
|
1017
|
+
* YOLO feed PaddleOCR recognition, rotated regions included.
|
|
1018
|
+
*
|
|
1019
|
+
* There is no `strategy` parameter. ppu's 'per-line' and 'cross-line' merge
|
|
1020
|
+
* boxes before reading them; the contract here is one result per box handed in,
|
|
1021
|
+
* which only 'per-box' can honour.
|
|
1022
|
+
*/
|
|
1023
|
+
const PADDLE_RECOGNIZER_DEFAULTS = Object.freeze({
|
|
1024
|
+
...BASE_STAGE_DEFAULTS,
|
|
1025
|
+
inputCol: "image",
|
|
1026
|
+
inputCols: ["image", "boxes"],
|
|
1027
|
+
outputCol: "text",
|
|
1028
|
+
keepInputData: true,
|
|
1029
|
+
preset: DEFAULT_OCR_PRESET,
|
|
1030
|
+
scaleFactor: 1,
|
|
1031
|
+
padding: 5,
|
|
1032
|
+
scoreThreshold: .5,
|
|
1033
|
+
keepFormatting: false,
|
|
1034
|
+
lineTolerance: 0,
|
|
1035
|
+
detectLineOrientation: false,
|
|
1036
|
+
onlyRotated: false,
|
|
1037
|
+
oriModel: DEFAULT_ORIENTATION_MODEL,
|
|
1038
|
+
spaceRecovery: false,
|
|
1039
|
+
recBatchSize: 6
|
|
1040
|
+
});
|
|
1041
|
+
/**
|
|
1042
|
+
* Tallest sheet crops are stacked onto.
|
|
1043
|
+
*
|
|
1044
|
+
* Well under every browser's canvas ceiling, and low enough that a sheet stays
|
|
1045
|
+
* cheap to allocate. A single crop taller than this gets a sheet to itself.
|
|
1046
|
+
*/
|
|
1047
|
+
const MAX_SHEET_HEIGHT = 8192;
|
|
1048
|
+
function boxesOf$1(source) {
|
|
1049
|
+
if (typeof source !== "object" || source === null) return [];
|
|
1050
|
+
return source.bboxes ?? [];
|
|
1051
|
+
}
|
|
1052
|
+
var PaddleRecognizer = class extends Stage {
|
|
1053
|
+
name = "PaddleRecognizer";
|
|
1054
|
+
orientation = null;
|
|
1055
|
+
recognition = null;
|
|
1056
|
+
constructor(options = {}) {
|
|
1057
|
+
super(resolveParams(PADDLE_RECOGNIZER_DEFAULTS, options, {
|
|
1058
|
+
inputCols: (value) => {
|
|
1059
|
+
if (value.length !== 2) throw new RangeError("inputCols must be [imageColumn, boxColumn]");
|
|
1060
|
+
},
|
|
1061
|
+
preset: validatePreset
|
|
1062
|
+
}));
|
|
1063
|
+
}
|
|
1064
|
+
async init() {
|
|
1065
|
+
const { preset, recBatchSize, spaceRecovery, detectLineOrientation, oriModel } = this.params;
|
|
1066
|
+
this.recognition ??= await getPaddleRecognizer(preset, {
|
|
1067
|
+
recBatchSize,
|
|
1068
|
+
spaceRecovery
|
|
1069
|
+
});
|
|
1070
|
+
if (detectLineOrientation) this.orientation ??= new LineOrientationClassifier(oriModel);
|
|
1071
|
+
}
|
|
1072
|
+
async apply(_input, row, ctx) {
|
|
1073
|
+
const { inputCols, scaleFactor, padding, scoreThreshold, keepFormatting, lineTolerance, detectLineOrientation, onlyRotated } = this.params;
|
|
1074
|
+
const [imageCol, boxCol] = inputCols;
|
|
1075
|
+
const image = row[imageCol];
|
|
1076
|
+
if (image?.exception) throw new OcrError(`Upstream stage failed: ${image.exception}`, this.name);
|
|
1077
|
+
if (!image || !(image.data instanceof Uint8Array) || image.data.byteLength === 0) throw new OcrError("Expected an Image with decoded bytes", this.name);
|
|
1078
|
+
const source = row[boxCol];
|
|
1079
|
+
if (source === void 0) throw new OcrError(`No boxes in column "${boxCol}". This stage reads a detector's output; run a text detector before it.`, this.name);
|
|
1080
|
+
await this.init();
|
|
1081
|
+
const recognition = this.recognition;
|
|
1082
|
+
const bitmap = await decodeImage(image.data);
|
|
1083
|
+
const canvas = scaleFactor === 1 ? bitmap : resize(bitmap, scaleFactor);
|
|
1084
|
+
const kept = [];
|
|
1085
|
+
const crops = [];
|
|
1086
|
+
try {
|
|
1087
|
+
for (const box of boxesOf$1(source)) {
|
|
1088
|
+
ctx.signal?.throwIfAborted();
|
|
1089
|
+
let crop = cropBox(canvas, box, {
|
|
1090
|
+
scaleFactor,
|
|
1091
|
+
padding
|
|
1092
|
+
});
|
|
1093
|
+
let inverted = false;
|
|
1094
|
+
if (detectLineOrientation && this.orientation) {
|
|
1095
|
+
inverted = await this.orientation.classify(crop) === "180_degree";
|
|
1096
|
+
if (inverted) crop = rotate180(crop);
|
|
1097
|
+
}
|
|
1098
|
+
if (onlyRotated && !isRotated(box) && !inverted) continue;
|
|
1099
|
+
kept.push(box);
|
|
1100
|
+
crops.push(crop);
|
|
1101
|
+
}
|
|
1102
|
+
} finally {
|
|
1103
|
+
bitmap.close();
|
|
1104
|
+
}
|
|
1105
|
+
const read = await recognizeCrops(recognition, crops, ctx);
|
|
1106
|
+
const bboxes = [];
|
|
1107
|
+
for (const [i, box] of kept.entries()) {
|
|
1108
|
+
const result = read[i];
|
|
1109
|
+
if (!result?.text) continue;
|
|
1110
|
+
if (result.score < scoreThreshold) continue;
|
|
1111
|
+
bboxes.push({
|
|
1112
|
+
...box,
|
|
1113
|
+
text: result.text,
|
|
1114
|
+
score: result.score
|
|
1115
|
+
});
|
|
1116
|
+
}
|
|
1117
|
+
return createDocument({
|
|
1118
|
+
path: String(row[this.params.pathCol] ?? image.path),
|
|
1119
|
+
type: "paddle-recognizer",
|
|
1120
|
+
text: keepFormatting ? boxesToFormattedText(bboxes, lineTolerance) : boxesToText(bboxes),
|
|
1121
|
+
bboxes
|
|
1122
|
+
});
|
|
1123
|
+
}
|
|
1124
|
+
onError(message, row) {
|
|
1125
|
+
return createDocument({
|
|
1126
|
+
path: String(row[this.params.pathCol] ?? "memory"),
|
|
1127
|
+
type: "paddle-recognizer",
|
|
1128
|
+
exception: message
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
async dispose() {
|
|
1132
|
+
await this.orientation?.dispose();
|
|
1133
|
+
this.orientation = null;
|
|
1134
|
+
this.recognition = null;
|
|
1135
|
+
}
|
|
1136
|
+
};
|
|
1137
|
+
/**
|
|
1138
|
+
* Read every crop, batching across them.
|
|
1139
|
+
*
|
|
1140
|
+
* ppu batches `recBatchSize` crops into a single inference, but only within one
|
|
1141
|
+
* `run()` call, and `run()` cuts its crops out of the one canvas it is given. So
|
|
1142
|
+
* the crops are stacked onto sheet canvases -- each at x 0, one below the last --
|
|
1143
|
+
* and handed back as the boxes to read. ppu re-crops exactly those rects, so the
|
|
1144
|
+
* unused width beside a narrow crop is never sampled. Calling `run()` once per
|
|
1145
|
+
* box would instead pay one inference, and one main-thread yield, per line.
|
|
1146
|
+
*
|
|
1147
|
+
* Results come back index-aligned to `crops`; a crop ppu rejected or did not
|
|
1148
|
+
* return is left `undefined`. `run()` sorts what it returns into reading order,
|
|
1149
|
+
* so results are matched on the slot's y offset, never on array position.
|
|
1150
|
+
*/
|
|
1151
|
+
async function recognizeCrops(recognition, crops, ctx) {
|
|
1152
|
+
const out = new Array(crops.length);
|
|
1153
|
+
for (let start = 0; start < crops.length;) {
|
|
1154
|
+
ctx.signal?.throwIfAborted();
|
|
1155
|
+
let end = start;
|
|
1156
|
+
let height = 0;
|
|
1157
|
+
let width = 0;
|
|
1158
|
+
while (end < crops.length) {
|
|
1159
|
+
const crop = crops[end];
|
|
1160
|
+
if (end > start && height + crop.height > MAX_SHEET_HEIGHT) break;
|
|
1161
|
+
height += crop.height;
|
|
1162
|
+
width = Math.max(width, crop.width);
|
|
1163
|
+
end++;
|
|
1164
|
+
}
|
|
1165
|
+
const sheet = createCanvas(width, height);
|
|
1166
|
+
const sheetCtx = context2d(sheet);
|
|
1167
|
+
const slots = [];
|
|
1168
|
+
const atOffset = /* @__PURE__ */ new Map();
|
|
1169
|
+
let offset = 0;
|
|
1170
|
+
for (let i = start; i < end; i++) {
|
|
1171
|
+
const crop = crops[i];
|
|
1172
|
+
sheetCtx.drawImage(crop, 0, offset);
|
|
1173
|
+
slots.push({
|
|
1174
|
+
x: 0,
|
|
1175
|
+
y: offset,
|
|
1176
|
+
width: crop.width,
|
|
1177
|
+
height: crop.height
|
|
1178
|
+
});
|
|
1179
|
+
atOffset.set(offset, i);
|
|
1180
|
+
offset += crop.height;
|
|
1181
|
+
}
|
|
1182
|
+
const results = await recognition.run(sheet, slots, void 0, "per-box");
|
|
1183
|
+
for (const result of results) {
|
|
1184
|
+
const index = atOffset.get(result.box.y);
|
|
1185
|
+
if (index !== void 0) out[index] = {
|
|
1186
|
+
text: result.text.trim(),
|
|
1187
|
+
score: result.confidence
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
start = end;
|
|
1191
|
+
}
|
|
1192
|
+
return out;
|
|
1193
|
+
}
|
|
1194
|
+
//#endregion
|
|
928
1195
|
//#region src/ocr/script-detect.ts
|
|
929
1196
|
/**
|
|
930
1197
|
* Writing-script detection via Tesseract's OSD model.
|
|
@@ -1278,17 +1545,7 @@ function wordBox(rect, geometry, inverted, scaleFactor, text, score) {
|
|
|
1278
1545
|
score
|
|
1279
1546
|
});
|
|
1280
1547
|
}
|
|
1281
|
-
/** Turn a crop 180 degrees, so an inverted line reads the right way up. */
|
|
1282
|
-
function rotate180(source) {
|
|
1283
|
-
const out = new OffscreenCanvas(source.width, source.height);
|
|
1284
|
-
const ctx = out.getContext("2d");
|
|
1285
|
-
if (!ctx) throw new OcrError("Failed to acquire a 2D context", "TesseractRecognizer");
|
|
1286
|
-
ctx.translate(source.width / 2, source.height / 2);
|
|
1287
|
-
ctx.rotate(Math.PI);
|
|
1288
|
-
ctx.drawImage(source, -source.width / 2, -source.height / 2);
|
|
1289
|
-
return out;
|
|
1290
|
-
}
|
|
1291
1548
|
//#endregion
|
|
1292
|
-
export {
|
|
1549
|
+
export { LineOrientationClassifier as A, boxScore as B, PADDLE_OCR_PRESETS as C, LINE_ORIENTATION_DEFAULTS as D, validatePreset as E, DBNET_DETECTOR_DEFAULTS as F, unclipRect as G, miniBox as H, DBNET_INPUT_SIZE as I, DEFAULT_DBNET_MODEL as L, DEFAULT_DETECTOR_ID as M, DETECTOR_MODELS as N, LineOrientationDetector as O, getDetectorModel as P, DbnetOnnxDetector as R, DEFAULT_OCR_PRESET as S, presetsForScript as T, orderPointsClockwise as U, findComponentBoundaries as V, quadsFromProbabilityMap as W, getPaddleRecognizer as _, TesseractOcr as a, loadPreset as b, disposeScriptDetection as c, PaddleRecognizer as d, PADDLE_TEXT_DETECTOR_DEFAULTS as f, disposePaddleServices as g, PaddleTextRecognizer as h, TESSERACT_OCR_DEFAULTS as i, ORIENTATION_INPUT as j, DEFAULT_ORIENTATION_MODEL as k, suggestPresets as l, PaddleTextDetector as m, TesseractRecognizer as n, disposeTesseract as o, PADDLE_TEXT_RECOGNIZER_DEFAULTS as p, DEFAULT_TESSDATA_URL as r, detectScript as s, TESSERACT_RECOGNIZER_DEFAULTS as t, PADDLE_RECOGNIZER_DEFAULTS as u, getPaddleService as v, isKnownPreset as w, removePreset as x, isPresetCached as y, DB_POSTPROCESS_DEFAULTS as z };
|
|
1293
1550
|
|
|
1294
|
-
//# sourceMappingURL=ocr-
|
|
1551
|
+
//# sourceMappingURL=ocr-Dkkbk0Wl.js.map
|