@graciousstar/node-red-contrib-vision-tools 1.0.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.
@@ -0,0 +1,713 @@
1
+ <script type="text/html" data-template-name="golden-compare">
2
+ <div class="form-row">
3
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
4
+ <input type="text" id="node-input-name" placeholder="Name" />
5
+ </div>
6
+ <div class="form-row">
7
+ <label for="node-input-goldenPath"
8
+ ><i class="fa fa-image"></i> Golden image path</label
9
+ >
10
+ <input
11
+ type="text"
12
+ id="node-input-goldenPath"
13
+ placeholder="/data/golden/reference.png (container path)"
14
+ />
15
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
16
+ >or send <code>msg.golden</code> (path/Buffer) per message</span
17
+ >
18
+ </div>
19
+ <div class="form-row">
20
+ <label for="node-input-workingSize"
21
+ ><i class="fa fa-arrows-alt"></i> Working size</label
22
+ >
23
+ <input
24
+ type="number"
25
+ id="node-input-workingSize"
26
+ min="64"
27
+ max="4096"
28
+ style="width:90px;"
29
+ />
30
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
31
+ >long edge, px - compare resolution, not the source resolution</span
32
+ >
33
+ </div>
34
+ <div class="form-row">
35
+ <label for="node-input-threshold"
36
+ ><i class="fa fa-adjust"></i> Threshold</label
37
+ >
38
+ <input
39
+ type="number"
40
+ id="node-input-threshold"
41
+ min="0"
42
+ max="255"
43
+ style="width:80px;"
44
+ />
45
+ <label for="node-input-alignSearch" style="margin-left:24px;"
46
+ >Align search</label
47
+ >
48
+ <input
49
+ type="number"
50
+ id="node-input-alignSearch"
51
+ min="0"
52
+ max="200"
53
+ style="width:70px;"
54
+ />
55
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
56
+ >px, working resolution - extra translation slack</span
57
+ >
58
+ </div>
59
+ <div class="form-row">
60
+ <label for="node-input-thresholdMode"
61
+ ><i class="fa fa-sliders"></i> Ink level</label
62
+ >
63
+ <select id="node-input-thresholdMode" style="width:150px;">
64
+ <option value="fixed">Fixed level</option>
65
+ <option value="otsu">Otsu (per image)</option>
66
+ <option value="sauvola">Sauvola (per pixel)</option>
67
+ </select>
68
+ <label for="node-input-sauvolaRadius" style="margin-left:24px;"
69
+ >Sauvola radius</label
70
+ >
71
+ <input
72
+ type="number"
73
+ id="node-input-sauvolaRadius"
74
+ min="2"
75
+ max="200"
76
+ style="width:70px;"
77
+ />
78
+ <input
79
+ type="number"
80
+ id="node-input-sauvolaK"
81
+ min="0.01"
82
+ max="1"
83
+ step="0.05"
84
+ style="width:70px; margin-left:6px;"
85
+ />
86
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
87
+ >otsu/sauvola re-derive the level per image, so exposure drift stops
88
+ mattering; radius/k apply to sauvola only</span
89
+ >
90
+ </div>
91
+ <div class="form-row">
92
+ <label for="node-input-inkMargin"
93
+ ><i class="fa fa-adjust"></i> Ink margin</label
94
+ >
95
+ <input
96
+ type="number"
97
+ id="node-input-inkMargin"
98
+ min="0"
99
+ max="128"
100
+ style="width:70px;"
101
+ />
102
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
103
+ >grey levels: pixels that cleared the ink level by less than this are ink
104
+ for alignment but can't raise a defect - stops a screened tint that prints
105
+ heavier than the artwork reporting itself as a blemish. 0 disables</span
106
+ >
107
+ </div>
108
+ <div class="form-row">
109
+ <label for="node-input-transformFilePath"
110
+ ><i class="fa fa-crosshairs"></i> Trained transform</label
111
+ >
112
+ <input
113
+ type="text"
114
+ id="node-input-transformFilePath"
115
+ placeholder="/data/golden/transform.json"
116
+ />
117
+ </div>
118
+ <div class="form-row">
119
+ <label for="node-input-trainTransform" style="width:auto;"
120
+ ><input
121
+ type="checkbox"
122
+ id="node-input-trainTransform"
123
+ style="width:auto; vertical-align:top;"
124
+ />
125
+ Train the transform from the next frame</label
126
+ >
127
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
128
+ >measures magnification and press stretch once and writes them to the file
129
+ above; later frames reuse them and solve only position and angle. Send
130
+ <code>msg.golden</code> with <code>msg.payload</code> to train from any
131
+ two images. Untick when trained, or drive it per message with
132
+ <code>msg.trainTransform</code></span
133
+ >
134
+ </div>
135
+ <div class="form-row">
136
+ <label for="node-input-mismatchScore"
137
+ ><i class="fa fa-question-circle"></i> Mismatch score</label
138
+ >
139
+ <input
140
+ type="number"
141
+ id="node-input-mismatchScore"
142
+ min="0"
143
+ max="1"
144
+ step="0.01"
145
+ style="width:70px;"
146
+ />
147
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
148
+ >warn "this looks like a different label" when the alignment residual
149
+ reaches this <em>and</em> both blemish checks are saturated. Comparing one
150
+ product's artwork against another's photo reports a thousand regions and a
151
+ 6% defect ratio, which reads as a catastrophic print rather than the wrong
152
+ golden. 0 disables</span
153
+ >
154
+ </div>
155
+ <div class="form-row">
156
+ <label for="node-input-workers"
157
+ ><i class="fa fa-tasks"></i> Worker threads</label
158
+ >
159
+ <input
160
+ type="number"
161
+ id="node-input-workers"
162
+ min="0"
163
+ max="64"
164
+ style="width:70px;"
165
+ />
166
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
167
+ >the per-pixel stages (warp, tile refinement, threshold, diff) are split
168
+ across a persistent pool. 0 picks one per core up to 16; 1 keeps everything
169
+ on the main thread. Results are identical either way - only the time
170
+ changes. Both summed-area tables run here too, and the alignment polish
171
+ keeps improving to about twelve workers on a 16-core host</span
172
+ >
173
+ </div>
174
+ <div class="form-row">
175
+ <label for="node-input-nativeFastAlign" style="width:auto;"
176
+ ><input
177
+ type="checkbox"
178
+ id="node-input-nativeFastAlign"
179
+ style="width:auto; vertical-align:top;"
180
+ />
181
+ Experimental OpenCV fast alignment</label
182
+ >
183
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
184
+ >lets OpenCV solve and warp the full affine alignment, bypassing the JS
185
+ tables, sweeps, polish, and global warp. Much faster, but deliberately
186
+ allowed to change the transform and inspection result. Implausible
187
+ angle/scale or a poor score automatically falls back to JS</span
188
+ >
189
+ </div>
190
+ <div class="form-row">
191
+ <label for="node-input-localAlign" style="width:auto;"
192
+ ><input
193
+ type="checkbox"
194
+ id="node-input-localAlign"
195
+ style="width:auto; vertical-align:top;"
196
+ />
197
+ Refine alignment per tile</label
198
+ >
199
+ <label for="node-input-localAlignTile" style="margin-left:24px; width:auto;"
200
+ >tile</label
201
+ >
202
+ <input
203
+ type="number"
204
+ id="node-input-localAlignTile"
205
+ min="16"
206
+ max="512"
207
+ style="width:70px; margin-left:6px;"
208
+ />
209
+ <label for="node-input-localAlignMax" style="margin-left:12px; width:auto;"
210
+ >max px</label
211
+ >
212
+ <input
213
+ type="number"
214
+ id="node-input-localAlignMax"
215
+ min="1"
216
+ max="16"
217
+ style="width:60px; margin-left:6px;"
218
+ />
219
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
220
+ >a label on a formed tray is not flat, so parts of it sit a few pixels off
221
+ however good the global fit is. Each tile takes up its own offset, capped
222
+ at "max px" so a tile can never slide far enough to hide a fault</span
223
+ >
224
+ </div>
225
+ <div class="form-row">
226
+ <label for="node-input-alignCandidates"
227
+ ><i class="fa fa-code-fork"></i> Align candidates</label
228
+ >
229
+ <input
230
+ type="number"
231
+ id="node-input-alignCandidates"
232
+ min="1"
233
+ max="16"
234
+ style="width:70px;"
235
+ />
236
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
237
+ >how many scale hypotheses survive the coarse search to be judged on real
238
+ pixel disagreement. 1 is the old greedy behaviour, where one bad coarse
239
+ guess misaligns the whole frame and cannot be recovered</span
240
+ >
241
+ </div>
242
+ <div class="form-row">
243
+ <label for="node-input-scaleSearchMin"
244
+ ><i class="fa fa-search-plus"></i> Scale search</label
245
+ >
246
+ <input
247
+ type="number"
248
+ id="node-input-scaleSearchMin"
249
+ min="0.1"
250
+ max="10"
251
+ step="0.05"
252
+ style="width:70px;"
253
+ />
254
+ <input
255
+ type="number"
256
+ id="node-input-scaleSearchMax"
257
+ min="0.1"
258
+ max="10"
259
+ step="0.05"
260
+ style="width:70px; margin-left:6px;"
261
+ />
262
+ <label for="node-input-scaleSearchSteps" style="margin-left:24px;"
263
+ >Steps</label
264
+ >
265
+ <input
266
+ type="number"
267
+ id="node-input-scaleSearchSteps"
268
+ min="1"
269
+ max="61"
270
+ style="width:70px;"
271
+ />
272
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
273
+ >min/max magnification of golden within the frame; set both to 1 to pin
274
+ the scale</span
275
+ >
276
+ </div>
277
+ <div class="form-row">
278
+ <label for="node-input-maxAspect"
279
+ ><i class="fa fa-arrows-v"></i> Stretch search</label
280
+ >
281
+ <input
282
+ type="number"
283
+ id="node-input-maxAspect"
284
+ min="0"
285
+ max="0.5"
286
+ step="0.01"
287
+ style="width:70px;"
288
+ />
289
+ <label for="node-input-aspectSteps" style="margin-left:24px;">Steps</label>
290
+ <input
291
+ type="number"
292
+ id="node-input-aspectSteps"
293
+ min="1"
294
+ max="21"
295
+ style="width:70px;"
296
+ />
297
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
298
+ >how far the print may be stretched along one axis vs the artwork (0.06 =
299
+ 6%); 0 assumes the two axes share a scale</span
300
+ >
301
+ </div>
302
+ <div class="form-row">
303
+ <label for="node-input-maxAngleDeg"
304
+ ><i class="fa fa-rotate-right"></i> Angle search</label
305
+ >
306
+ <input
307
+ type="number"
308
+ id="node-input-maxAngleDeg"
309
+ min="0"
310
+ max="30"
311
+ step="0.5"
312
+ style="width:70px;"
313
+ />
314
+ <label for="node-input-angleSteps" style="margin-left:24px;">Steps</label>
315
+ <input
316
+ type="number"
317
+ id="node-input-angleSteps"
318
+ min="1"
319
+ max="21"
320
+ style="width:70px;"
321
+ />
322
+ <label for="node-input-positionToleranceAngleDeg" style="margin-left:24px;"
323
+ >Angle tolerance</label
324
+ >
325
+ <input
326
+ type="number"
327
+ id="node-input-positionToleranceAngleDeg"
328
+ min="0"
329
+ max="30"
330
+ step="0.1"
331
+ style="width:70px;"
332
+ />
333
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
334
+ >degrees searched either way, and how far off square still passes</span
335
+ >
336
+ </div>
337
+ <div class="form-row">
338
+ <label><i class="fa fa-tint"></i> Blemish tolerance (px)</label>
339
+ <label for="node-input-printTolerance" style="width:auto; margin-right:4px;"
340
+ >Print</label
341
+ >
342
+ <input
343
+ type="number"
344
+ id="node-input-printTolerance"
345
+ min="0"
346
+ max="50"
347
+ style="width:60px;"
348
+ />
349
+ <label
350
+ for="node-input-backgroundTolerance"
351
+ style="width:auto; margin-left:16px; margin-right:4px;"
352
+ >Background</label
353
+ >
354
+ <input
355
+ type="number"
356
+ id="node-input-backgroundTolerance"
357
+ min="0"
358
+ max="50"
359
+ style="width:60px;"
360
+ />
361
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
362
+ >independent checks: missing ink (print) vs. unwanted ink
363
+ (background)</span
364
+ >
365
+ </div>
366
+ <div class="form-row">
367
+ <label for="node-input-scaleFilePath"
368
+ ><i class="fa fa-crosshairs"></i> Scale file path</label
369
+ >
370
+ <input
371
+ type="text"
372
+ id="node-input-scaleFilePath"
373
+ placeholder="/data/golden/calibration.json (optional)"
374
+ />
375
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
376
+ >from <code>checkerboard-calibrate</code> - enables mm position tolerances
377
+ below</span
378
+ >
379
+ </div>
380
+ <div class="form-row">
381
+ <label><i class="fa fa-arrows"></i> Position tolerance</label>
382
+ <label
383
+ for="node-input-positionToleranceXMm"
384
+ style="width:auto; margin-right:4px;"
385
+ >X [mm]</label
386
+ >
387
+ <input
388
+ type="number"
389
+ id="node-input-positionToleranceXMm"
390
+ min="0"
391
+ step="0.1"
392
+ style="width:60px;"
393
+ />
394
+ <label
395
+ for="node-input-positionToleranceYMm"
396
+ style="width:auto; margin-left:16px; margin-right:4px;"
397
+ >Y [mm]</label
398
+ >
399
+ <input
400
+ type="number"
401
+ id="node-input-positionToleranceYMm"
402
+ min="0"
403
+ step="0.1"
404
+ style="width:60px;"
405
+ />
406
+ </div>
407
+ <div class="form-row">
408
+ <label>&nbsp;</label>
409
+ <label
410
+ for="node-input-positionToleranceXPx"
411
+ style="width:auto; margin-right:4px;"
412
+ >X [px]</label
413
+ >
414
+ <input
415
+ type="number"
416
+ id="node-input-positionToleranceXPx"
417
+ min="0"
418
+ style="width:60px;"
419
+ />
420
+ <label
421
+ for="node-input-positionToleranceYPx"
422
+ style="width:auto; margin-left:16px; margin-right:4px;"
423
+ >Y [px]</label
424
+ >
425
+ <input
426
+ type="number"
427
+ id="node-input-positionToleranceYPx"
428
+ min="0"
429
+ style="width:60px;"
430
+ />
431
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
432
+ >px fallback, used when no scale file is configured/found</span
433
+ >
434
+ </div>
435
+ <div class="form-row">
436
+ <label for="node-input-blockSize"
437
+ ><i class="fa fa-th"></i> Heat map block</label
438
+ >
439
+ <input
440
+ type="number"
441
+ id="node-input-blockSize"
442
+ min="4"
443
+ max="256"
444
+ style="width:70px;"
445
+ />
446
+ <label for="node-input-blockThreshold" style="margin-left:24px;"
447
+ >Block threshold</label
448
+ >
449
+ <input
450
+ type="number"
451
+ id="node-input-blockThreshold"
452
+ min="0"
453
+ max="1"
454
+ step="0.01"
455
+ style="width:70px;"
456
+ />
457
+ </div>
458
+ <div class="form-row">
459
+ <label for="node-input-failThreshold"
460
+ ><i class="fa fa-exclamation-triangle"></i> Fail threshold</label
461
+ >
462
+ <input
463
+ type="number"
464
+ id="node-input-failThreshold"
465
+ min="0"
466
+ max="1"
467
+ step="0.01"
468
+ style="width:70px;"
469
+ />
470
+ <label for="node-input-failRatio" style="margin-left:24px;"
471
+ >Fail ratio</label
472
+ >
473
+ <input
474
+ type="number"
475
+ id="node-input-failRatio"
476
+ min="0"
477
+ max="1"
478
+ step="0.001"
479
+ style="width:80px;"
480
+ />
481
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
482
+ >applies to both print and background checks</span
483
+ >
484
+ </div>
485
+ <div class="form-row">
486
+ <label for="node-input-outputPrintHeatmap"
487
+ ><i class="fa fa-picture-o"></i> Output heat maps</label
488
+ >
489
+ <input
490
+ type="checkbox"
491
+ id="node-input-outputPrintHeatmap"
492
+ style="display:inline-block; width:auto; vertical-align:middle;"
493
+ />
494
+ <span style="margin-left:4px;">print</span>
495
+ <input
496
+ type="checkbox"
497
+ id="node-input-outputBackgroundHeatmap"
498
+ style="display:inline-block; width:auto; vertical-align:middle; margin-left:16px;"
499
+ />
500
+ <span style="margin-left:4px;">background</span>
501
+ </div>
502
+ <div class="form-row">
503
+ <label for="node-input-debugStages"
504
+ ><i class="fa fa-eye"></i> Output pipeline stages</label
505
+ >
506
+ <input
507
+ type="checkbox"
508
+ id="node-input-debugStages"
509
+ style="display:inline-block; width:auto; vertical-align:middle;"
510
+ />
511
+ <span class="node-input-hint" style="margin-left:10px; color:#999;"
512
+ >adds <code>msg.stages</code> for debugging a bad match - leave off in
513
+ production</span
514
+ >
515
+ </div>
516
+ </script>
517
+
518
+ <script type="text/html" data-help-name="golden-compare">
519
+ <p>
520
+ Golden-template AOI: a
521
+ <b>position check</b> (deviation of a matched crop window from the center of
522
+ the available margin, vs. a tolerance band - a real position defect fails on
523
+ its own, it isn't silently corrected away) plus two independent
524
+ <b>blemish checks</b> - print (golden has ink the target is missing) and
525
+ background (target has ink the golden never has) - each with its own
526
+ tolerance. Built for speed - the golden reference is decoded and
527
+ preprocessed once and cached, not redone per frame; decode/resize uses
528
+ <code>sharp</code> (native).
529
+ </p>
530
+ <p>
531
+ The target is decoded preserving its own aspect ratio (never stretched to
532
+ fit), then a sliding-window search finds the golden-sized window within it
533
+ that best matches - a raw camera capture with extra background/margin
534
+ doesn't need a manual crop step.
535
+ <b>Only translation is corrected this way</b> (bounded by the target's
536
+ actual size plus <i>Align search</i> px of extra slack) - rotation and scale
537
+ drift are not. This assumes a mechanically- fixtured camera/part. The found
538
+ window position is also what the position check measures.
539
+ </p>
540
+
541
+ <h3>Input</h3>
542
+ <p><code>msg.payload</code> can be:</p>
543
+ <ul>
544
+ <li>a <b>Buffer</b> / Uint8Array with the image bytes</li>
545
+ <li>a <b>string path</b> to an image file on the container's filesystem</li>
546
+ <li>
547
+ an object with <code>data</code> / <code>buffer</code> or
548
+ <code>path</code>
549
+ </li>
550
+ </ul>
551
+ <p>
552
+ Optional per-message overrides: <code>msg.golden</code> (path or Buffer -
553
+ swaps and re-caches the golden reference if it differs from what's cached),
554
+ <code>msg.threshold</code>, <code>msg.printTolerance</code>,
555
+ <code>msg.backgroundTolerance</code>, <code>msg.alignSearch</code>,
556
+ <code>msg.thresholdMode</code>, <code>msg.sauvolaRadius</code>,
557
+ <code>msg.sauvolaK</code>, <code>msg.inkMargin</code>,
558
+ <code>msg.trainTransform</code>, <code>msg.nativeAlignSeed</code>,
559
+ <code>msg.nativeFastAlign</code>, <code>msg.workers</code>,
560
+ <code>msg.localAlign</code>, <code>msg.localAlignTile</code>,
561
+ <code>msg.localAlignMax</code>, <code>msg.scaleSearchMin</code>,
562
+ <code>msg.scaleSearchMax</code>, <code>msg.scaleSearchSteps</code>,
563
+ <code>msg.maxAspect</code>, <code>msg.aspectSteps</code>,
564
+ <code>msg.maxAngleDeg</code>, <code>msg.angleSteps</code>,
565
+ <code>msg.alignCandidates</code>, <code>msg.mismatchScore</code>,
566
+ <code>msg.positionToleranceAngleDeg</code>,
567
+ <code>msg.positionToleranceXMm</code
568
+ >/<code>YMm</code>/<code>XPx</code>/<code>YPx</code>,
569
+ <code>msg.blockSize</code>, <code>msg.blockThreshold</code>,
570
+ <code>msg.failThreshold</code>, <code>msg.failRatio</code>,
571
+ <code>msg.outputPrintHeatmap</code>,
572
+ <code>msg.outputBackgroundHeatmap</code>, <code>msg.debugStages</code>.
573
+ </p>
574
+
575
+ <h3>Output</h3>
576
+ <ul>
577
+ <li>
578
+ <code>msg.payload</code> — <code>true</code>/<code>false</code> overall
579
+ pass
580
+ </li>
581
+ <li>
582
+ <code>msg.result</code> —
583
+ <code
584
+ >{ pass, position: { dxPx, dyPx, dxMm, dyMm, angleDeg, anglePass, scale,
585
+ scaleX, scaleY, stretchPercent, pass }, transform: { pinned, native, nativeFallback?, seeded, pinRefused?, scaleX,
586
+ scaleY, scale, stretchPercent, angleDeg, ox, oy, score }, match: {
587
+ score, grade, mismatchSuspected, reason }, thresholds: { golden, target
588
+ }, localAlign: { tiles, localised, meanPx, medianPx, maxPx },
589
+ printBlemish: { pass, defectRatio, regions }, backgroundBlemish: { pass,
590
+ defectRatio, regions } }</code
591
+ >
592
+ (region coordinates in the working-resolution image, same size as the heat
593
+ maps)
594
+ </li>
595
+ <li>
596
+ <code>msg.printHeatmap</code> / <code>msg.backgroundHeatmap</code> — PNG
597
+ <code>Buffer</code> overlays (only if the matching
598
+ <i>Output heat maps</i> checkbox is on)
599
+ </li>
600
+ <li>
601
+ <code>msg.timings</code> —
602
+ <code>{ decodeMs, alignMs, diffMs, heatmapMs, stagesMs, totalMs }</code>
603
+ </li>
604
+ <li>
605
+ <code>msg.stages</code> — only if <i>Output pipeline stages</i> is on: PNG
606
+ <code>Buffer</code>s for each pipeline step (<code>goldenGray</code>,
607
+ <code>goldenFg</code>, <code>goldenFgDilatedBackground</code>,
608
+ <code>targetGray</code>, <code>targetFg</code>,
609
+ <code>targetGrayAligned</code>, <code>targetFgAligned</code>,
610
+ <code>targetFgDilatedPrint</code>, <code>printDefect</code>,
611
+ <code>backgroundDefect</code>)
612
+ </li>
613
+ <li>
614
+ the node logs the same breakdown plus the position/print/background
615
+ verdicts at info level
616
+ </li>
617
+ </ul>
618
+
619
+ <h3>Calibration</h3>
620
+ <p>
621
+ <i>Scale file path</i> points at the JSON baseline written by
622
+ <code>checkerboard-calibrate</code> (<code
623
+ >{ mmPerPixelNative, nativeWidth, nativeHeight, calibratedAt }</code
624
+ >). When set and the file exists, the position check converts the measured
625
+ shift to mm (rescaled from the calibration photo's native resolution to this
626
+ node's <i>Working size</i>) and checks it against the mm tolerances. Without
627
+ it, the px tolerances are used instead - position is still checked, just not
628
+ in physical units.
629
+ </p>
630
+
631
+ <h3>Notes</h3>
632
+ <p>
633
+ <i>Golden image path</i> is a path <b>inside the container</b> - use
634
+ <code>/data</code> for anything that should survive a rebuild. The golden
635
+ reference is only re-decoded when its source (path/Buffer fingerprint),
636
+ <i>Working size</i>/<i>Threshold</i>/<i>Background tolerance</i>, or the
637
+ calibrated scale changes - <i>Print tolerance</i>/<i>Align search</i>/etc.
638
+ are applied fresh per frame and don't need a re-decode.
639
+ </p>
640
+ </script>
641
+
642
+ <script type="text/javascript">
643
+ RED.nodes.registerType("golden-compare", {
644
+ category: "vision",
645
+ color: "#B7950B",
646
+ defaults: {
647
+ // number(true) = blank allowed. Node-RED does not backfill new
648
+ // defaults into node instances that were saved before the property
649
+ // existed, so a strict validator marks every such node "invalid
650
+ // properties" the moment a setting is added. The runtime clamps a
651
+ // missing or blank value to the same default anyway, so accepting
652
+ // blank here is both safe and what the user means by it.
653
+ name: { value: "" },
654
+ goldenPath: { value: "" },
655
+ workingSize: { value: 1024, validate: RED.validators.number(true) },
656
+ threshold: { value: 128, validate: RED.validators.number(true) },
657
+ thresholdMode: { value: "otsu" },
658
+ sauvolaRadius: { value: 24, validate: RED.validators.number(true) },
659
+ sauvolaK: { value: 0.2, validate: RED.validators.number(true) },
660
+ inkMargin: { value: 8, validate: RED.validators.number(true) },
661
+ alignCandidates: { value: 5, validate: RED.validators.number(true) },
662
+ workers: { value: 0, validate: RED.validators.number(true) },
663
+ nativeFastAlign: { value: false },
664
+ mismatchScore: { value: 0.15, validate: RED.validators.number(true) },
665
+ localAlign: { value: true },
666
+ localAlignTile: { value: 96, validate: RED.validators.number(true) },
667
+ localAlignMax: { value: 3, validate: RED.validators.number(true) },
668
+ transformFilePath: { value: "" },
669
+ trainTransform: { value: false },
670
+ scaleSearchMin: { value: 0.6, validate: RED.validators.number(true) },
671
+ scaleSearchMax: { value: 2.5, validate: RED.validators.number(true) },
672
+ scaleSearchSteps: { value: 19, validate: RED.validators.number(true) },
673
+ maxAspect: { value: 0.06, validate: RED.validators.number(true) },
674
+ aspectSteps: { value: 7, validate: RED.validators.number(true) },
675
+ maxAngleDeg: { value: 2, validate: RED.validators.number(true) },
676
+ angleSteps: { value: 5, validate: RED.validators.number(true) },
677
+ positionToleranceAngleDeg: {
678
+ value: 1,
679
+ validate: RED.validators.number(true),
680
+ },
681
+ printTolerance: { value: 2, validate: RED.validators.number(true) },
682
+ backgroundTolerance: { value: 1, validate: RED.validators.number(true) },
683
+ alignSearch: { value: 16, validate: RED.validators.number(true) },
684
+ scaleFilePath: { value: "" },
685
+ positionToleranceXMm: { value: 2, validate: RED.validators.number(true) },
686
+ positionToleranceYMm: { value: 2, validate: RED.validators.number(true) },
687
+ positionToleranceXPx: {
688
+ value: 16,
689
+ validate: RED.validators.number(true),
690
+ },
691
+ positionToleranceYPx: {
692
+ value: 16,
693
+ validate: RED.validators.number(true),
694
+ },
695
+ blockSize: { value: 16, validate: RED.validators.number(true) },
696
+ blockThreshold: { value: 0.15, validate: RED.validators.number(true) },
697
+ failThreshold: { value: 0.3, validate: RED.validators.number(true) },
698
+ failRatio: { value: 0.002, validate: RED.validators.number(true) },
699
+ outputPrintHeatmap: { value: true },
700
+ outputBackgroundHeatmap: { value: true },
701
+ debugStages: { value: false },
702
+ },
703
+ inputs: 1,
704
+ outputs: 1,
705
+ icon: "golden-compare.svg",
706
+ label: function () {
707
+ return this.name || "golden compare";
708
+ },
709
+ paletteLabel: function () {
710
+ return "golden compare";
711
+ },
712
+ });
713
+ </script>