Skip to content
russoeduPublic

About

scanmate is a suite of tools to validate scanned documents in comparison with the digital original

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

264 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

scanmate

scanmate

Tools for checking what came back. A document goes out as a PDF; a scan or a photograph of it comes back turned, rescaled and shadowed, with something written on it — and perhaps something changed. These packages put the two back on the same coordinates and answer three questions: was it signed where it should be, is everything that must be there still there, and did anything change?

the evidence page: the original and the returned scan side by side, every finding drawn on both

The audit's evidence page. Green: a field that was filled in. Orange: the room a signature is given to stray past its box. Red: text that reads differently — here, one printed digit of the account number replaced by another of the same run. Made from the IRS Form W-9 (a work of the United States government, in the public domain).

The pipeline

flowchart LR
  A[photos or PDFs] --> B[merge]
  B --> C[extract]
  C --> D[align]
  D --> E[enhance]
  E --> F[ocr]
  D --> G[diff]
  F --> H[find]
  F --> I[audit]
  G --> I
  H --> I
  I --> J[verdict + evidence]
Loading

Seven packages. @scanmate/scan holds the whole pipeline; the other six stand on their own for anyone who needs one of them without the rest - and without the rest's dependencies on disk.

Packages

package what it answers
@scanmate/scan All of it. new Scanmate(issued, returned) with a method per comparison, each running what it needs, remembering what it did, and loading a stage package only when it is used. It also holds the stages that only make sense inside the pipeline: the pixel comparison (what changed, which boxes are ticked), enhancement, the content search and the audit - one verdict with its evidence, its evidence PDF and its calibration.
@scanmate/ocr Does the scan still say what the original said? Read run by run against the original's text layer, with printed figures matched glyph by glyph against the original's own ink.
@scanmate/align Where does this scan sit on the original? Deskewed, rescaled and registered, with a confidence you can act on.
@scanmate/extract What is in this PDF, and where are its fields? Pages as rasters at the scan's own resolution, page kind, real resolution, the text layer with its geometry - and fields placed from the labels it prints.
@scanmate/seal Is a signed PDF still the document that was signed? Each signature checked against the bytes it covers: the digest, the signature, what it leaves uncovered, and who the certificate says signed. Not whether that signer is trusted - that is a trust list, not a file.
@scanmate/merge How do these photos become one document? Without re-encoding what is already good, keeping each page's real resolution - and regions drawn onto a PDF to check them.
@scanmate/ink The kernel: ink separation, warps, matrices, correlation, text normalisation, the synthesis helpers your own tests want, and the contracts the stages pass along. Every bit of it is re-exported by @scanmate/scan, so installing the facade is enough - depend on ink directly when it is the only thing you need.

@scanmate/diff, @scanmate/find, @scanmate/enhance and @scanmate/audit were separate packages until 0.17.0; everything they exported is exported by @scanmate/scan from 0.18.0, under the same names. @scanmate/image-fix, the first version of all this, is gone. All five are deprecated on npm.

Every stage takes pages and hands the same pages back, carrying what it found: align adds aligned, enhance adds enhanced, ocr adds text, diff adds diff, find adds find, audit adds audit. So the stages compose in any order their inputs allow, nothing has to be matched up again by index, and whatever you attached to a page is still on it at the end. extract and merge are the ends of the line: they turn documents into pages, and pages back into a document.

Getting started

npm install @scanmate/scan @scanmate/extract @scanmate/align
import { auditPages } from '@scanmate/scan'
import { alignPages } from '@scanmate/align'
import { extractPair } from '@scanmate/extract'

const { pages } = await extractPair({ original: 'fw9-issued.pdf', scanned: 'fw9-returned.pdf' })
const audit = await auditPages(await alignPages(pages), {
  // The W-9's signature row, measured off the form in points from the page's top-left.
  expected: [
    { page: 1, id: 'signature', x: 120, y: 577, width: 262, height: 22 },
    { page: 1, id: 'date',      x: 404, y: 577, width: 171, height: 22 },
  ],
})

audit.verdict                    // 'pass' | 'review'
audit.pages[0].audit.reasons     // why, one sentence each
audit.pages[0].audit.findings    // text and pixel findings, merged by place
audit.pages[0].audit.evidenceImage   // the page above
audit.pages[0].original          // the page itself is still there

How it decides

Each package documents its own algorithms — what every step measures, the decision flows, and every constant with the measurement behind it:

  • scan — what runs when, what is remembered, and what loads
  • audit — correlating two comparisons, and the verdict
  • ocr — matching by place, the recheck, and verifying figures against the print
  • pixel comparison — ink, tolerance, and deciding a region
  • content search — approximate search that will not approximate a figure
  • align — the coarse guess, features, RANSAC, and choosing a model
  • extract — page kind, real resolution, and the text layer
  • enhancement — dividing by the paper
  • merge — never encoding twice
  • ink — the primitives, and why the codec boundary is where it is

Two things worth knowing

A scan's resolution decides what can be verified. Measured on real returned documents, by the resolution the printed content has on the scan: at 125 dpi every page read at 0.995–1.0 of the original with no differences at all; at 117 the white totals on coloured bars blur past reading; at 90 a third of the printed figures fail. Below about 120 dpi, a check that reports nothing has proved nothing — and the packages say so rather than passing the page.

A signature is ink, a forgery may not be. A digit replaced by another digit of the same size cannot be seen by any pixel comparison: the glyph is the document's own ink, and it sits inside the tolerance that alignment needs. That is why figures are matched against the original's own glyphs instead of read.

Working on it

npm install
npx nx run-many -t lint,typecheck,test,build   # what CI runs
npx nx sync:check                              # project references
npm run playground:start                       # the demo pipeline, end to end

The playground with no arguments generates a form, signs and ticks it, scans it crooked, too big, out of focus and under a shadow, then aligns it back and reports what it found. Point it at real files to check your own pages:

npm run playground:start -- \
  --original page1.png --scanned returned.jpg \
  --region signature:76,905,420,78 \
  --out ./out

An Nx monorepo, created with @mnci/cli. Releases run from main through nx release, versioned from conventional commits; RELEASE_SPECIFIER overrides the bump for one run when a version is a decision rather than a sum of its commits.

About

scanmate is a suite of tools to validate scanned documents in comparison with the digital original

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages