@scanmate/ocr 0.0.2 → 0.0.3
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/dist/index.esm.js
CHANGED
|
@@ -611,12 +611,12 @@ const DEFAULT_RECHECK_PASSES = [{
|
|
|
611
611
|
const FIGURE_CHARACTERS = '0123456789.,:/%()+-';
|
|
612
612
|
/** White kept around a crop, in pixels: tesseract reads poorly off the edge of an image. */
|
|
613
613
|
const MARGIN = 12;
|
|
614
|
-
async function recheckRun(engine, image, run, options) {
|
|
614
|
+
async function recheckRun(engine, image, run, options, polarity) {
|
|
615
615
|
const {
|
|
616
616
|
passes = DEFAULT_RECHECK_PASSES,
|
|
617
617
|
agree = 2
|
|
618
618
|
} = options;
|
|
619
|
-
const crop = cropRun(image.raster, image.dpi, run);
|
|
619
|
+
const crop = cropRun(image.raster, image.dpi, run, polarity);
|
|
620
620
|
if (crop === null) return {
|
|
621
621
|
cleared: false,
|
|
622
622
|
reading: null,
|
|
@@ -632,7 +632,8 @@ async function recheckRun(engine, image, run, options) {
|
|
|
632
632
|
tried++;
|
|
633
633
|
const scale = Math.max(1, pass.dpi / image.dpi);
|
|
634
634
|
const enlarged = scale === 1 ? crop : await resampleRaster(crop, Math.round(crop.width * scale), Math.round(crop.height * scale));
|
|
635
|
-
|
|
635
|
+
// Inverted text keeps the bar's grey behind it; only a stretch makes that paper white.
|
|
636
|
+
const prepared = frame(polarity === 'light-on-dark' || pass.stretch === true ? stretch(enlarged) : enlarged);
|
|
636
637
|
const read = await engine.recognise(prepared, {
|
|
637
638
|
layout: pass.layout,
|
|
638
639
|
characters
|
|
@@ -658,9 +659,10 @@ async function recheckRun(engine, image, run, options) {
|
|
|
658
659
|
}
|
|
659
660
|
/**
|
|
660
661
|
* The run's box on the image, grown by a quarter of its height (at least 2 pt)
|
|
661
|
-
* so the glyphs are whole; light text on a dark bar is turned dark on light
|
|
662
|
+
* so the glyphs are whole; light text on a dark bar is turned dark on light -
|
|
663
|
+
* as the original prints it when that is known, else when the crop is dark.
|
|
662
664
|
*/
|
|
663
|
-
function cropRun(raster, dpi, run) {
|
|
665
|
+
function cropRun(raster, dpi, run, polarity) {
|
|
664
666
|
const s = dpi / 72;
|
|
665
667
|
const pad = Math.max(2, run.height * 0.25);
|
|
666
668
|
const left = Math.max(0, Math.floor((run.x - pad) * s));
|
|
@@ -672,9 +674,29 @@ function cropRun(raster, dpi, run) {
|
|
|
672
674
|
for (let y = top; y < bottom; y++) out.data.set(raster.data.subarray((y * raster.width + left) * 4, (y * raster.width + right) * 4), (y - top) * out.width * 4);
|
|
673
675
|
let sum = 0;
|
|
674
676
|
for (let i = 0; i < out.data.length; i += 4) sum += luminance(out.data, i);
|
|
675
|
-
|
|
677
|
+
const invert = polarity === undefined ? sum / (out.width * out.height) < 110 : polarity === 'light-on-dark';
|
|
678
|
+
if (invert) for (let i = 0; i < out.data.length; i += 4) for (let c = 0; c < 3; c++) out.data[i + c] = 255 - out.data[i + c];
|
|
676
679
|
return out;
|
|
677
680
|
}
|
|
681
|
+
/**
|
|
682
|
+
* Which way the original prints a run, read off its own crisp rendering: the
|
|
683
|
+
* glyphs are the pixels far from the background, and the background is most of
|
|
684
|
+
* the box.
|
|
685
|
+
*/
|
|
686
|
+
function printPolarity(raster, dpi, run) {
|
|
687
|
+
const s = dpi / 72;
|
|
688
|
+
const left = Math.max(0, Math.floor(run.x * s));
|
|
689
|
+
const top = Math.max(0, Math.floor(run.y * s));
|
|
690
|
+
const right = Math.min(raster.width, Math.ceil((run.x + run.width) * s));
|
|
691
|
+
const bottom = Math.min(raster.height, Math.ceil((run.y + run.height) * s));
|
|
692
|
+
const values = [];
|
|
693
|
+
for (let y = top; y < bottom; y++) for (let x = left; x < right; x++) values.push(luminance(raster.data, (y * raster.width + x) * 4));
|
|
694
|
+
if (values.length === 0) return 'dark-on-light';
|
|
695
|
+
const background = values.toSorted((a, b) => a - b)[Math.floor(values.length / 2)];
|
|
696
|
+
const glyphs = values.filter(v => Math.abs(v - background) > 48);
|
|
697
|
+
if (glyphs.length === 0) return 'dark-on-light';
|
|
698
|
+
return glyphs.reduce((a, b) => a + b, 0) / glyphs.length > background ? 'light-on-dark' : 'dark-on-light';
|
|
699
|
+
}
|
|
678
700
|
/** Linear stretch so the darkest 2% become black and the lightest 2% white. */
|
|
679
701
|
function stretch(raster) {
|
|
680
702
|
const values = new Uint8Array(raster.width * raster.height);
|
|
@@ -837,7 +859,7 @@ async function readPage(page, engine, options) {
|
|
|
837
859
|
const second = await recheckRun(engine, scanImage, run, {
|
|
838
860
|
...rules,
|
|
839
861
|
...recheck
|
|
840
|
-
});
|
|
862
|
+
}, printPolarity(page.original.raster, originalDpi, run));
|
|
841
863
|
if (second.reading === null) continue;
|
|
842
864
|
claims.found[r] = second.reading;
|
|
843
865
|
rechecks.cleared++;
|
|
@@ -943,5 +965,5 @@ function mean(values) {
|
|
|
943
965
|
return values.length === 0 ? 0 : values.reduce((a, b) => a + b, 0) / values.length;
|
|
944
966
|
}
|
|
945
967
|
|
|
946
|
-
export { DEFAULT_NORMALISE, DEFAULT_RECHECK_PASSES, DEFAULT_TESSERACT_OPTIONS, claimWords, compareTexts, cosine, createTesseractEngine, diacriticsMap, dice, foldConfusables, foldDiacritics, jaccard, jaroWinkler, judgeRun, judgeRuns, levenshtein, levenshteinSimilarity, matchWords, normaliseText, ocrPages, recheckRun, tokenise, wordDistance, wordRecall };
|
|
968
|
+
export { DEFAULT_NORMALISE, DEFAULT_RECHECK_PASSES, DEFAULT_TESSERACT_OPTIONS, claimWords, compareTexts, cosine, createTesseractEngine, diacriticsMap, dice, foldConfusables, foldDiacritics, jaccard, jaroWinkler, judgeRun, judgeRuns, levenshtein, levenshteinSimilarity, matchWords, normaliseText, ocrPages, printPolarity, recheckRun, tokenise, wordDistance, wordRecall };
|
|
947
969
|
//# sourceMappingURL=index.esm.js.map
|
package/dist/src/index.d.ts
CHANGED
|
@@ -30,8 +30,8 @@ export { compareTexts } from './text-similarity/index.js';
|
|
|
30
30
|
export type { ScoreMetric, TextMetrics } from './text-similarity/index.js';
|
|
31
31
|
export { DEFAULT_NORMALISE, normaliseText, tokenise } from './text-normalisation/index.js';
|
|
32
32
|
export type { NormaliseOptions } from './text-normalisation/index.js';
|
|
33
|
-
export { claimWords, DEFAULT_RECHECK_PASSES, judgeRun, judgeRuns, matchWords, recheckRun } from './page-reading/index.js';
|
|
34
|
-
export type { Claims, MatchOptions, Recheck, RecheckOptions, RecheckPass, Reference, Verdict, WordMatch } from './page-reading/index.js';
|
|
33
|
+
export { claimWords, DEFAULT_RECHECK_PASSES, judgeRun, judgeRuns, matchWords, printPolarity, recheckRun } from './page-reading/index.js';
|
|
34
|
+
export type { Claims, MatchOptions, PrintPolarity, Recheck, RecheckOptions, RecheckPass, Reference, Verdict, WordMatch } from './page-reading/index.js';
|
|
35
35
|
export { cosine, dice, jaccard, jaroWinkler, levenshtein, levenshteinSimilarity, wordDistance, wordRecall } from './text-similarity/index.js';
|
|
36
36
|
export { diacriticsMap, foldConfusables, foldDiacritics } from './text-normalisation/index.js';
|
|
37
37
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -3,6 +3,6 @@ export { claimWords, judgeRun, judgeRuns, matchWords } from './match-words.use-c
|
|
|
3
3
|
export type { Claims, MatchOptions, Reference, Verdict, WordMatch } from './match-words.use-case.js';
|
|
4
4
|
export { ocrPages } from './ocr-pages.use-case.js';
|
|
5
5
|
export type { OcrOptions, OcrReport, PageOcr, PlacedText, PositionedText, ReadablePage, RunReading, SideText, TextDifference } from './ocr-report.contract.js';
|
|
6
|
-
export { DEFAULT_RECHECK_PASSES, recheckRun } from './recheck-run.use-case.js';
|
|
7
|
-
export type { Recheck, RecheckOptions, RecheckPass } from './recheck-run.use-case.js';
|
|
6
|
+
export { DEFAULT_RECHECK_PASSES, printPolarity, recheckRun } from './recheck-run.use-case.js';
|
|
7
|
+
export type { PrintPolarity, Recheck, RecheckOptions, RecheckPass } from './recheck-run.use-case.js';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -9,7 +9,10 @@ import type { MatchOptions, Reference } from './match-words.use-case.js';
|
|
|
9
9
|
* a shaded bar - is at the mercy of the engine's layout analysis and of one
|
|
10
10
|
* resolution. Cropped to itself, enlarged further, read as a single line (or
|
|
11
11
|
* word, or as digits only when it is a figure), light-on-dark text inverted,
|
|
12
|
-
* contrast stretched, it often reads cleanly.
|
|
12
|
+
* contrast stretched, it often reads cleanly. Whether the text is light on a
|
|
13
|
+
* dark bar is taken from the original, where it is certain, rather than
|
|
14
|
+
* guessed from the scan: a white total on a purple bar can scan as pale lilac
|
|
15
|
+
* on paler lilac, too light overall to look like a dark bar. Measured on real scans at 120
|
|
13
16
|
* and 144 dpi, cropped multi-pass reading confirmed 96-97% of every printed
|
|
14
17
|
* value on the page; at 93 dpi, a third.
|
|
15
18
|
*
|
|
@@ -34,6 +37,8 @@ export interface RecheckOptions {
|
|
|
34
37
|
agree?: number;
|
|
35
38
|
}
|
|
36
39
|
export declare const DEFAULT_RECHECK_PASSES: readonly RecheckPass[];
|
|
40
|
+
/** Which way the original prints a run: dark text on light, or light text on a dark bar. */
|
|
41
|
+
export type PrintPolarity = 'dark-on-light' | 'light-on-dark';
|
|
37
42
|
export interface Recheck {
|
|
38
43
|
cleared: boolean;
|
|
39
44
|
/** An agreeing reading when cleared; `null` otherwise. */
|
|
@@ -45,5 +50,11 @@ export interface Recheck {
|
|
|
45
50
|
export declare function recheckRun(engine: OcrEngine, image: {
|
|
46
51
|
raster: Raster;
|
|
47
52
|
dpi: number;
|
|
48
|
-
}, run: Reference, options: MatchOptions & RecheckOptions): Promise<Recheck>;
|
|
53
|
+
}, run: Reference, options: MatchOptions & RecheckOptions, polarity?: PrintPolarity): Promise<Recheck>;
|
|
54
|
+
/**
|
|
55
|
+
* Which way the original prints a run, read off its own crisp rendering: the
|
|
56
|
+
* glyphs are the pixels far from the background, and the background is most of
|
|
57
|
+
* the box.
|
|
58
|
+
*/
|
|
59
|
+
export declare function printPolarity(raster: Raster, dpi: number, run: Reference): PrintPolarity;
|
|
49
60
|
//# sourceMappingURL=recheck-run.use-case.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scanmate/ocr",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "How closely a scan's text matches the original's: OCR matched run by run on the page, figures held exact, every difference located.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eduardo Russo",
|
|
@@ -51,6 +51,6 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@scanmate/align": "^0.0.2",
|
|
54
|
-
"@scanmate/extract": "^0.0.
|
|
54
|
+
"@scanmate/extract": "^0.0.3"
|
|
55
55
|
}
|
|
56
56
|
}
|