Testing, explained from the start
One concept at a time, with a checkpoint after each. If you can say the checkpoint sentences in a room, you understand the test well enough to defend it.
This page covers the Residue Station benchmark. The Residue Photo product has its own benchmark on real Library of Congress card scans, 1,222 real impostor pairs with zero false accepts, shown on the vault demo. Every number on this page comes from a physical model of a surface: published roughness statistics, three-light capture, real sensor noise, wear as removed depth. The model is stated in full so it can be attacked, and every method ran on identical data.
Forty identical parts, built in software to the physics of the real ones. Each object is a patch of surface, 512 pixels square at 10 microns per pixel, about 5 mm across, made of bumps at six sizes from 40 microns to 480 microns. The bump heights follow the same statistics real machined and moulded surfaces show.
Two things make the simulated objects realistic:
d microns replaces fine bumps faster than coarse ones. The rate is set by one number, kappa, how deep a bump runs compared with how wide it is. Kappa is swept across the whole published range, so the result does not depend on one value.The model is in pebbleid/surface.py in the private project and is restated in the browser inside every demo.
Every object is photographed once under clean conditions. That photo is its record, the same thing a plant would capture at enrolment. Then every object is photographed three more times, each with its own fresh wear, dust film, camera noise and a random light direction. Those are the reads.
Each read is shown to a matcher together with all forty records and the matcher is asked: which one is this? There are 40 objects and 3 reads each, so 120 answers per method per condition. Each read is also scored against the 39 records it does not belong to, which gives 4,680 "wrong pair" comparisons per method per condition. Those wrong pairs are what tell you how often a method would confuse two different objects.
One read is one 5 mm tile. A real camera view of a part covers about 78 such tiles, so the single-tile result is the hard version of the test.
One condition would prove nothing, because we chose the difficulty. So the test is a grid. A centre point is held fixed and one thing is changed at a time, then one cell turns everything bad at once.
| Axis | Centre | Values swept | What it stands for |
|---|---|---|---|
| Material removed | 10 µm | 0, 1, 3, 10, 30 | Wear between record and read. One reactor pass is about 1 µm; a bead blast is tens |
| Kappa | 0.5 | 0.1, 0.5 | How shallow the surface features are. 0.1 is the pessimistic end of the published evidence |
| Shared fraction | 0.5 | 0.3, 0.5, 0.9 | How much of the surface the whole batch has in common. 0.9 is a near-perfect mould |
| Dust | 0.2 | 0, 0.2, 0.5 | A film dimming the read unevenly |
| Sampling | 10 µm/px | 5, 10, 20 | Camera resolution at the surface |
| Corner | 30 µm, kappa 0.1, shared 0.9, dust 0.5 | Everything bad at once | |
Twelve one-axis cells plus the corner is 13. Every method runs in every cell on identical data.
"28 methods" means 28 matcher variants ran the same test. They fall into five families. All but the last two families see a single photo under one oblique light at a random direction, which is what a competitor without three-light hardware has.
| Family | Members | Why it is in the test |
|---|---|---|
| Keypoint matching | ORB, SIFT, AKAZE, BRISK | What published surface-fingerprint methods rely on. Finds distinctive points and counts how many agree on one shift |
| Texture codes | LBP histogram; Gabor code at 1 scale and at 6 scales | The iris-recognition construction and the classic texture descriptor |
| Plain correlation | raw correlation; phase correlation then correlation; windowed band-limited phase correlation | The obvious engineer's shortcut: line the pictures up and compare them |
| Correlation with the batch removed | raw correlation, shared pattern subtracted | Tests whether subtraction alone is the trick |
| Three-light with the batch removed | PCA subspace, equal weights and fitted weights; our legacy residual; ours v1; ours v2 and its variants (fixed weights, trimmed mean, median with per-read gain, LDA weights) | Everything that has our capture hardware, so the comparison isolates the matcher |
The PCA rows matter most. PCA is a textbook technique anyone can implement in an afternoon. Giving it our three-light photos and our band filters asks the fair question: once a rival has the hardware, is our matcher worth anything on top?
Each method gives a number for how alike two pictures are. Look at the numbers it gives for same-object pairs and for different-object pairs. d′ is the gap between those two averages, divided by their spread. Under 1 means the two piles overlap and the method is near a coin flip. In the tens, the piles are far apart. In the hundreds, the model is saying the margin is enormous, not that anyone would measure that exact figure.
Simpler: of 120 reads, how many times was the top-scoring record the wrong object? Where a method makes zero errors we do not report zero. With 120 trials the true rate could still be as high as 3 in 120, so 2.5% is what is shown. That is the rule of three, and it is the right way to report a clean run.
Every d′ carries a 95% interval from a bootstrap that resamples whole objects, not individual reads, because three reads of one object are not independent of each other.
Dividing the gap only by the spread of the different-object scores makes keypoint methods look superb: SIFT scores 26.7 that way while naming the wrong object 87% of the time, because almost all of its different-object scores are exactly zero. That number is kept in the full tables and nothing is ranked on it.
Three findings, in the order we learned them.
| Method | Centre | 30 µm wear | 90% shared | 50% dust | Corner |
|---|---|---|---|---|---|
| Ours v2 | 391 | 168 | 367 | 372 | 17.0 |
| PCA, same weighting | 358 | 170 | 334 | 334 | 14.4 |
| Ours v1, equal weights | 40.9 | 31.8 | 25.5 | 19.5 | 2.0 |
| Raw correlation, shared subtracted | 53 | 38 | 23 | 13 | 1.2 |
| SIFT keypoints | 0.41 | 0.38 | 0.29 | 0.25 | negative |
| ORB keypoints | 0.39 | 0.35 | 0.31 | 0.09 | negative |
d′ per 5 mm tile. One real camera view composes about 78 tiles.
The size of the lead. If the PCA rival is also given our band weighting, v2 is 10% to 30% ahead, and level in 3 of 13 conditions. That is the matcher's margin on its own, and it is why the moat is the system around the matcher: the capture rig, closed-inventory tracking, scan-again-never-guess, and the error budget.
"Ablation" means removing one part of the design at a time and rerunning the test, so you learn which part bought which share of the result. Our pipeline is three moves on the image, plus the decision rule. Here is each move taken out, at the centre condition.
| Ingredient removed | d′ with it | d′ without it | Worth |
|---|---|---|---|
| Three-light capture (use one oblique photo at a random light direction) | 40.9 | 1.2 | 29 to 39 times. The big one. Without it, 47 of 120 reads name the wrong object |
| Shared-pattern subtraction | 40.9 | 27.8 | 1.4 to 1.6 times at the centre; 3.5 times when 90% of the surface is shared |
| Band filters, equal weights | 40.9 | 80.6 | Negative. Unweighted, the filters cost a factor of 2. This is the v1 flaw |
| Band filters, fitted weights (v2) | 391 | 80.6 | 4.5 to 5.2 times. The filters earn their place once weighted |
Ratios are paired bootstrap intervals: the same objects go through both versions.
The third row is the one to notice. An ablation that only ever says "every part helps" is suspect. This one found a part that hurt, and the fix for it became v2.
Kappa is the one input no literature gives us. So a second run holds everything else at the centre and sweeps kappa down to the pessimistic end while material is removed, using a full camera view (78 tiles) and realistic sensor noise: photon shot noise, read noise, 10-bit quantisation, a random sub-pixel shift on every read. The bar is d′ 9.8, which is what under one false match a year requires in the pebble-reactor case, the hardest application we have sized.
| Material removed | kappa 0.1 | kappa 0.25 | kappa 1.0 |
|---|---|---|---|
| 1 µm, about one reactor pass | 340 | 353 | 361 |
| 3 µm | 299 | 335 | 356 |
| 10 µm | 212 | 282 | 340 |
| 30 µm | 118 | 196 | 299 |
| Requirement | 9.8 | 9.8 | 9.8 |
d′ per view, 24 objects per cell, all bands. Fine features die first: the 40 µm band alone falls from 2,556 to 153 over this sweep while the 160 µm band falls from 924 to 275.
Two corrections were made during this run and are recorded, because a test that never found a bug in itself is not being read carefully. A band-pass filter nominally tuned to 40 µm was mostly reporting 600 µm content, which made fine scales look far more wear-resistant than they are. And any shared pattern the subtraction failed to remove was being credited to the genuine score; separation is now measured from the impostor mean, not from zero.
What d′ in the hundreds means. The margin, not a headline accuracy. In a running system the binding event is a retaken capture, not a false match, which is why every demo shows its retake rate.
The grid varies the surface. A third run varies the capture, applied to the read only while enrolment stays clean, since that is what a plant sees.
Two mechanisms came out of it. A quality detector on the capture side, blind to the truth, flags saturated, dark and abnormally textured pixels, and the read is taken again when too much of the window is flagged. On clean reads it flags 0.00% of the window, so it does not send good captures back. And a registration step that uses the shared batch pattern as a landmark recovers rotation to about 0.01 degrees, scale to 0.1% and shift to 0.05 px, including the 216 px case, in about half a second per read.
The one inherited shortcut is stated in the log: the benchmark estimates the shared pattern while excluding the read's true object, which a plant cannot know. The effect shrinks as the batch grows and is listed as an open item rather than hidden.
For any system that answers "is this the same one?", the standard is ISO/IEC 19795, biometric performance testing and reporting. It asks for the false-match rate and the false-non-match rate at a stated threshold, on data you did not tune on, with confidence intervals, and ideally run by someone who is not the vendor. The gold example is NIST's face-recognition vendor test: vendors submit, NIST runs it, everyone sees the same curve. In surface fingerprinting nobody publishes to that standard; the incumbent's public figure is a marketing line ("99.9%, zero false positives") with no test behind it that a reader can check.
| The standard asks for | What we report | Status |
|---|---|---|
| False-match and false-non-match rates at a stated threshold | d′, rank-1 error, and in every demo a threshold derived from the application's comparison count with the retake rate shown | Model rates, every demo |
| Confidence intervals | 95% bootstrap over objects; rule of three for clean runs | None |
| Test data independent of tuning | Design choices made on one random seed, final numbers reported on another | Separate seeds, thirteen conditions |
| Held-out, representative data | Thirteen conditions spanning the published range of the unknowns | Covers the published range |
| Independent execution | Reproducible from the project: one command regenerates the grid | Open to an outside run |
| Failure-to-acquire reported separately | The quality gate sends the read back for another view and the retake rate is shown in each demo | Shown in every demo |
Every rate on this site is reported in the standard's vocabulary and its arithmetic: benchmarked on a physical model, reported in ISO/IEC 19795 terms, reproducible by anyone who runs the command.