@scanmate/diff 0.7.1 → 0.9.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/README.md CHANGED
@@ -48,7 +48,9 @@ Rectangles are in PDF points from the page's top-left by default, the same frame
48
48
 
49
49
  A region is **identified** when it has at least `minFillArea` (2 mm²) of new ink and is not **overfilled** — covered or struck through, which `maxFill` (0.5) draws the line on. Form rules showing through a slight misregistration are discounted: a component spanning 90% of the region and no thicker than 0.6 mm is the box's own printed line.
50
50
 
51
- People sign past the box they are given, so each region also claims the ink within `expectedMargin` (6 points) of it, and the regions claim it **together**, so one stroke running through two fields is not left over as an unexpected mark. What a region reports is still the rectangle it was given; the band is drawn in pink.
51
+ People sign past the box they are given, so each region also claims the ink within its **bleed** - 6 points on every side unless told otherwise - and the regions claim it **together**, so one stroke running through two fields is not left over as an unexpected mark. What a region reports is still the rectangle it was given; the band is drawn in pink.
52
+
53
+ The bleed can be set per side: `bleed` for all four, and `bleedTop`, `bleedRight`, `bleedBottom` or `bleedLeft` to override one. A signature descends more than it climbs, so `{ bleedTop: 2, bleedBottom: 12 }` keeps a field clear of the printed line above it and gives the pen room below. `Scanmate.mark` in `@scanmate/scan` draws exactly this band on the original, so a region can be checked before anything is measured with it.
52
54
 
53
55
  Each region reports its shape too — how many separate changes, the largest, the bounds as a share of the box, how much ink touches the border — so a signature can be told from a stray line without looking at the picture.
54
56
 
@@ -61,7 +63,7 @@ Each region reports its shape too — how many separate changes, the largest, th
61
63
  | `faintInk` | `0.25` | Fraction of the normal threshold for "still there". |
62
64
  | `minFillArea` | `2` mm² | New ink a region needs. |
63
65
  | `maxFill` | `0.5` | Above this the region is covered, not filled. |
64
- | `expectedMargin` | `6` pt | How far outside a region its ink may lie. |
66
+ | `bleed` | `6` pt | How far outside a region its ink may lie, every side. `bleedTop`, `bleedRight`, `bleedBottom` and `bleedLeft` override one side. |
65
67
  | `formLineSpan` | `0.9` | Span that makes a component a printed rule... |
66
68
  | `formLineThickness` | `0.6` mm | ...if it is no thicker than this. |
67
69
  | `minChangeArea` | `1` mm² | Smallest change reported. |
package/dist/index.esm.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createRaster, decodeImage, binarize, inkMap, toGrayscale, otsuThreshold, dilate, coverage, encodeImage } from '@scanmate/ink';
1
+ import { createRaster, decodeImage, binarize, inkMap, toGrayscale, otsuThreshold, dilate, coverage, resolveBleed, growBy, hasBleed, encodeImage } from '@scanmate/ink';
2
2
 
3
3
  /** The area in question, drawn on the original: a statement of where, not of what. */
4
4
  const REFERENCE = [0, 23, 252, 255];
@@ -720,7 +720,6 @@ async function diffPage(page, expected = [], options = {}) {
720
720
  mergeGap = 3,
721
721
  assumeDpi = 150,
722
722
  regionOverlap = 0.5,
723
- expectedMargin = 6,
724
723
  maxChanges = 50,
725
724
  probes = [],
726
725
  keepMasks = false,
@@ -736,10 +735,11 @@ async function diffPage(page, expected = [], options = {}) {
736
735
  const masks = await buildMasks(page.original.raster, page.aligned.raster, ink, tolerance, faintInk);
737
736
  // People sign past the box they are given, so each region claims the ink a little
738
737
  // way outside it too; what it reports is still the region it was given.
738
+ const bleed = resolveBleed(options);
739
739
  const regions = expected.map(e => ({
740
740
  id: e.id,
741
741
  rect: scaleRect(e, toPixels),
742
- claim: scaleRect(grow(e, expectedMargin), toPixels)
742
+ claim: scaleRect(growBy(e, bleed), toPixels)
743
743
  }));
744
744
  const findChanges = (mask, minArea) => {
745
745
  const components = connectedComponents(mask).filter(c => c.pixels >= 2);
@@ -806,28 +806,28 @@ async function diffPage(page, expected = [], options = {}) {
806
806
  const unexpected = outside.slice(0, maxChanges).map(box => toChange(box));
807
807
  const missing = lost.slice(0, maxChanges).map(box => toChange(box));
808
808
  // The band first, so a region's own outline draws over it where they meet.
809
- const margins = expectedMargin > 0 ? regions.map(region => ({
809
+ const margins = hasBleed(bleed) ? regions.map(region => ({
810
810
  rect: region.claim,
811
811
  color: EXPECTED_MARGIN
812
812
  })) : [];
813
813
  const verdicts = regions.map((region, i) => ({
814
- rect: grow(region.rect, 2),
814
+ rect: pad(region.rect, 2),
815
815
  color: expectedResults[i].identified ? IDENTIFIED : NOT_IDENTIFIED
816
816
  }));
817
817
  const reported = [...margins, ...verdicts, ...outside.slice(0, maxChanges).map(box => ({
818
- rect: grow(box, 4),
818
+ rect: pad(box, 4),
819
819
  color: UNEXPECTED
820
820
  }))];
821
821
  // On the original, every region is simply the area in question; the answers belong to the scan.
822
822
  const asAsked = regions.map(region => ({
823
- rect: grow(region.rect, 2),
823
+ rect: pad(region.rect, 2),
824
824
  color: REFERENCE
825
825
  }));
826
826
  const diffRaster = paintOverlay(masks);
827
827
  if (annotate) annotateOverlay(diffRaster, reported);
828
828
  // Lines about a point thick at any dpi, so the boxes read the same on every page.
829
829
  const losses = lost.slice(0, maxChanges).map(box => ({
830
- rect: grow(box, 4),
830
+ rect: pad(box, 4),
831
831
  color: MISSING
832
832
  }));
833
833
  const sideBySideRaster = sideBySide ? composeSideBySide(page.original.raster, page.aligned.raster, {
@@ -925,7 +925,12 @@ function scaleRect(rect, factor) {
925
925
  height: rect.height * factor
926
926
  };
927
927
  }
928
- function grow(rect, by) {
928
+ /**
929
+ * A box drawn a little outside what it marks, in pixels, so the outline does not
930
+ * cover the ink it is pointing at. Purely how the evidence is drawn; the room a
931
+ * region is allowed is its bleed, and that is decided by `resolveBleed`.
932
+ */
933
+ function pad(rect, by) {
929
934
  return {
930
935
  x: rect.x - by,
931
936
  y: rect.y - by,
@@ -1,4 +1,4 @@
1
- import type { AlignedPage, ImageFormat, InkOptions, ProgressCallback, Raster, ScanmateRect } from '@scanmate/ink';
1
+ import type { AlignedPage, Bleed, ImageFormat, InkOptions, ProgressCallback, Raster, ScanmateRect } from '@scanmate/ink';
2
2
  import type { Masks } from '../region-comparison/index.js';
3
3
  /**
4
4
  * Coordinates for regions going in and changes coming out.
@@ -33,7 +33,24 @@ export interface ExpectedChange {
33
33
  export type ComparedPage<Page extends AlignedPage = AlignedPage> = Page & {
34
34
  diff: PageDiff;
35
35
  };
36
- export interface DiffOptions {
36
+ /**
37
+ * How the comparison is run.
38
+ *
39
+ * The bleed - `bleed`, and `bleedTop`, `bleedRight`, `bleedBottom`, `bleedLeft`
40
+ * to override a side - is how far outside an expected region its ink may still
41
+ * lie, in `units`. Default 6 on every side, 2 mm at 72 points to the inch.
42
+ *
43
+ * People sign past the box they are given - a descender below the rule, a
44
+ * flourish out to the side - and that is the signature, not a mark someone made
45
+ * elsewhere. The region claims the ink within its bleed and measures it, while
46
+ * still reporting the rectangle it was given. Ink inside the bleed of any
47
+ * expected region counts towards them all, so one stroke crossing two fields is
48
+ * not left over as unexpected.
49
+ *
50
+ * It is the same rule `Scanmate.mark` draws, so the band a reviewer is shown is
51
+ * the band that is measured.
52
+ */
53
+ export interface DiffOptions extends Bleed {
37
54
  /** Units of `ExpectedChange` rectangles and of every rectangle reported back. Default `'points'`. */
38
55
  units?: CoordinateUnits;
39
56
  /**
@@ -56,17 +73,6 @@ export interface DiffOptions {
56
73
  * 150 dpi, so a caller is expected to drop them as soon as it has asked.
57
74
  */
58
75
  keepMasks?: boolean;
59
- /**
60
- * How far outside an expected region its ink may still lie, in `units`. Default `6`
61
- * (2 mm at 72 points to the inch).
62
- *
63
- * People sign past the box they are given - a descender below the rule, a flourish
64
- * out to the side - and that is the signature, not a mark someone made elsewhere. The
65
- * region claims the ink within this band and measures it, while still reporting the
66
- * rectangle it was given. Ink inside the band of any expected region counts towards
67
- * them all, so one stroke crossing two fields is not left over as unexpected.
68
- */
69
- expectedMargin?: number;
70
76
  /** Pixels the original's ink is fattened by before diffing, to absorb sub-pixel misalignment. Default `2`. */
71
77
  tolerance?: number;
72
78
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scanmate/diff",
3
- "version": "0.7.1",
3
+ "version": "0.9.0",
4
4
  "description": "What changed between an original and its aligned scan: expected regions filled in, unexpected marks, lost ink, and a side-by-side evidence image.",
5
5
  "license": "MIT",
6
6
  "author": "Eduardo Russo",
@@ -39,12 +39,12 @@
39
39
  "!**/*.js.map"
40
40
  ],
41
41
  "dependencies": {
42
- "@scanmate/ink": "^0.7.1"
42
+ "@scanmate/ink": "^0.9.0"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"
46
46
  },
47
47
  "devDependencies": {
48
- "@scanmate/align": "^0.7.1"
48
+ "@scanmate/align": "^0.9.0"
49
49
  }
50
50
  }