@scanmate/ocr 0.5.0 → 0.6.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.
package/dist/index.esm.js CHANGED
@@ -975,13 +975,21 @@ function verifyPrintedRun(original, scan, dpi, run, templates, options = {}) {
975
975
  minPrinted = MIN_PRINTED_SCORE,
976
976
  minRivals = scope === 'text' ? MIN_TEXT_RIVALS : MIN_RIVALS,
977
977
  minDigits = 2,
978
- characters = scope === 'text' ? TEXT_CHARACTERS : FIGURE_CHARACTERS$1
978
+ characters = scope === 'figures' ? FIGURE_CHARACTERS$1 : TEXT_CHARACTERS,
979
+ claimed
979
980
  } = options;
980
981
  const printed = [...run.text].filter(character => character.trim() !== '');
981
- const wanted = scope === 'text' ? textCells(printed, characters) : figureCells(printed, minDigits);
982
+ // What the reading says it is, lined up with what the original printed. The
983
+ // two must correspond character for character or the comparison is meaningless.
984
+ const proposed = scope === 'confirm' ? [...(claimed ?? '')].filter(character => character.trim() !== '') : null;
985
+ if (proposed !== null && (proposed.length === 0 || proposed.length !== printed.length)) return {
986
+ verified: false,
987
+ because: 'no-claim'
988
+ };
989
+ const wanted = proposed === null ? scope === 'text' ? textCells(printed, characters) : figureCells(printed, minDigits) : disputedCells(printed, proposed, characters);
982
990
  if (wanted.size === 0) return {
983
991
  verified: false,
984
- because: 'no-figure'
992
+ because: proposed === null ? 'no-figure' : 'no-claim'
985
993
  };
986
994
  // A total on a shaded bar is printed light on dark; turned round, it is a figure like any other.
987
995
  const lightOnDark = printPolarity(original, dpi, run) === 'light-on-dark';
@@ -992,9 +1000,25 @@ function verifyPrintedRun(original, scan, dpi, run, templates, options = {}) {
992
1000
  verified: false,
993
1001
  because: 'unplaceable'
994
1002
  };
995
- // Rivals: every character the page prints in this face and size that a figure could use.
1003
+ /**
1004
+ * Who each cell is up against.
1005
+ *
1006
+ * Identifying an unknown glyph needs most of the alphabet: to assert "this is
1007
+ * a 7" you must be able to rule the others out, and the bar of eight digits
1008
+ * is what the false-call rate justified. A settlement is not asking that. The
1009
+ * reading has already named its answer, so the question is which of two named
1010
+ * characters this ink is - and two templates answer it. A wrong verdict then
1011
+ * requires the ink to match the *other specific character* better, not merely
1012
+ * to be hard to read.
1013
+ */
996
1014
  const rivals = [...characters].filter(character => templates.has(templateKey(run, character)));
997
- if (rivals.length < minRivals) return {
1015
+ const rivalsAt = index => {
1016
+ if (proposed === null) return rivals;
1017
+ const against = proposed[index];
1018
+ return against !== undefined && templates.has(templateKey(run, against)) ? [against] : [];
1019
+ };
1020
+ const enough = proposed === null ? rivals.length >= minRivals : [...wanted].some(index => rivalsAt(index).length > 0);
1021
+ if (!enough) return {
998
1022
  verified: false,
999
1023
  because: 'few-rivals'
1000
1024
  };
@@ -1042,7 +1066,7 @@ function verifyPrintedRun(original, scan, dpi, run, templates, options = {}) {
1042
1066
  let confidence = 1;
1043
1067
  let checked = 0;
1044
1068
  for (const [index, crop] of crops) {
1045
- const passes = recoveries.map(recovery => judgeCell(crop, printed[index], rivals, run, templates, recovery, limits));
1069
+ const passes = recoveries.map(recovery => judgeCell(crop, printed[index], rivalsAt(index), run, templates, recovery, limits));
1046
1070
  const first = passes[0];
1047
1071
  if (first === undefined) continue;
1048
1072
  // Undecided unless every pass reads it the same way: a verdict that changes
@@ -1116,6 +1140,15 @@ function textCells(printed, characters) {
1116
1140
  for (const [index, character] of printed.entries()) if (characters.includes(character)) wanted.add(index);
1117
1141
  return wanted;
1118
1142
  }
1143
+ /** Where the print and the reading disagree, which is the whole of what a settlement asks about. */
1144
+ function disputedCells(printed, proposed, characters) {
1145
+ const wanted = new Set();
1146
+ for (const [index, character] of printed.entries()) {
1147
+ const against = proposed[index];
1148
+ if (against !== undefined && against !== character && characters.includes(character) && characters.includes(against)) wanted.add(index);
1149
+ }
1150
+ return wanted;
1151
+ }
1119
1152
  function figureCells(printed, minDigits) {
1120
1153
  const wanted = new Set();
1121
1154
  let group = [];
@@ -1740,7 +1773,8 @@ async function readPage(page, engine, options, templates) {
1740
1773
  'unplaceable': 0,
1741
1774
  'few-rivals': 0,
1742
1775
  'too-coarse': 0,
1743
- 'undecided': 0
1776
+ 'undecided': 0,
1777
+ 'no-claim': 0
1744
1778
  }
1745
1779
  };
1746
1780
  const seenChanged = new Set();
@@ -70,10 +70,22 @@ export interface VerifyOptions {
70
70
  minRivals?: number;
71
71
  /**
72
72
  * What to check: the digits of a figure (`'figures'`, the default, cheap
73
- * enough for a whole page), or every letter and digit of the run (`'text'`,
74
- * for a single run under dispute).
73
+ * enough for a whole page), every letter and digit of the run (`'text'`, for
74
+ * a single run under dispute), or the one question a settlement actually
75
+ * asks (`'confirm'`).
76
+ *
77
+ * `'confirm'` needs {@link claimed} and answers "is this ink the character
78
+ * the original printed, or the one the reading says it is?" - which takes
79
+ * two templates rather than a near-complete alphabet, because the reading
80
+ * has already named the alternative. See {@link VerifyOptions.claimed}.
75
81
  */
76
- scope?: 'figures' | 'text';
82
+ scope?: 'figures' | 'text' | 'confirm';
83
+ /**
84
+ * What the reading claims the run says, for `scope: 'confirm'`. Compared
85
+ * character by character against the original's own text, so it has to hold
86
+ * the same number of printed characters; anything else abstains.
87
+ */
88
+ claimed?: string;
77
89
  /** How badly the printed glyph must match for a character to count as changed. Default `0.85`. */
78
90
  maxPrinted?: number;
79
91
  /** Digits in a row before a group is treated as a figure. Default `2`. */
@@ -109,8 +121,10 @@ export interface CellVerification {
109
121
  * - `too-coarse` - the scan resolves the cell below the height it is matched
110
122
  * at, so a verdict would rest on detail it never captured.
111
123
  * - `undecided` - measured, and nothing won by enough to say.
124
+ * - `no-claim` - `scope: 'confirm'` without a reading to test, or one that
125
+ * does not line up with the print character for character.
112
126
  */
113
- export type PrintAbstention = 'no-figure' | 'unplaceable' | 'few-rivals' | 'too-coarse' | 'undecided';
127
+ export type PrintAbstention = 'no-figure' | 'unplaceable' | 'few-rivals' | 'too-coarse' | 'undecided' | 'no-claim';
114
128
  /** What the ink said, or why it would not say. */
115
129
  export type PrintCheck = ({
116
130
  verified: true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scanmate/ocr",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
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",
@@ -41,7 +41,7 @@
41
41
  "!**/*.js.map"
42
42
  ],
43
43
  "dependencies": {
44
- "@scanmate/ink": "^0.5.0",
44
+ "@scanmate/ink": "^0.6.0",
45
45
  "@tesseract.js-data/eng": "^1.0.0",
46
46
  "fastest-levenshtein": "^1.0.16",
47
47
  "tesseract.js": "^7.0.0"
@@ -50,7 +50,7 @@
50
50
  "access": "public"
51
51
  },
52
52
  "devDependencies": {
53
- "@scanmate/align": "^0.5.0",
54
- "@scanmate/extract": "^0.5.0"
53
+ "@scanmate/align": "^0.6.0",
54
+ "@scanmate/extract": "^0.6.0"
55
55
  }
56
56
  }