Repository navigation
feat: read barcodes back out of images - #2
Merged
Merged
Conversation
Adds a `scan` feature that runs the rendering pipeline backwards and meets
it at the same seam:
image -> GrayImage -> binarize -> BitMatrix -> Decoder::decode -> payload
Nothing in src/scan/ matches on SymbologyKind. The one thing a scan line
needs to know about a symbology — how it groups bars and spaces into
fixed-width characters — is new SymbologyKind::linear_character metadata,
alongside required_quiet_zone and is_linear.
Pixel runs are converted to modules one character at a time rather than
against a single module-width estimate for the whole symbol, so a printed
width that drifts from nominal cannot accumulate into a misread. The
threshold is computed per block and smoothed across neighbours, which
follows a lighting gradient no global threshold can.
Every payload returned has passed Code 128's own check character: a smudge
is a miss, not a plausible wrong tracking number routed to the wrong depot.
Handled: any module width, extra margin or a crop flush to the bars, the
symbol anywhere in a larger image, light-on-dark, uneven lighting, moderate
speckle and blur, and a label applied on its side, since columns are swept
as well as rows. Not handled, and documented as such: rotation off the
axes, perspective, curved surfaces, and QR.
This reverses the "image scanning is out of scope" boundary in AGENTS.md
and the README. The boundary is rewritten rather than dropped: scanning is
in scope as far as it stays arithmetic. Camera-grade decoding is a scanning
engine and belongs in one. QR scanning stays out at a concrete cost — rqrr
would add seven crates to the published graph, so it remains a
dev-dependency verifying our own output.
No new dependencies; the resolved graph is unchanged. Builds no_std.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Entire-Checkpoint: 01M0MN8P909V0PRD86646T6KS4
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a
scanfeature that runs the rendering pipeline backwards, meeting it at the sameBitMatrixseam:Design
Nothing in
src/scan/matches onSymbologyKind. The one thing a scan line needs to know about a symbology — how it groups bars and spaces into fixed-width characters — is newSymbologyKind::linear_character()metadata, sitting alongsiderequired_quiet_zone()andis_linear(). A unit test asserts it agrees withis_linear()for every variant so the two cannot drift.Two decisions worth the review time:
survives_module_widths_that_do_not_divide_evenlyis the test that fails without this.follows_a_lighting_gradientbuilds an image where the darkest paper is darker than the lightest ink — no global threshold can separate it.A scan never returns a guess: every payload has passed Code 128's own check character. A wrong answer routes a parcel to the wrong depot and looks correct doing it, which is worse than a miss.
Scope
Handled: any module width; extra margin or a crop flush to the bars; the symbol anywhere in a larger image; light-on-dark; uneven lighting; moderate speckle and blur; a label applied on its side, since columns are swept as well as rows.
Not handled, and documented as such: rotation off the axes, perspective, curved surfaces, and QR. This reads rendered labels, flatbed scans and screenshots — not camera frames.
Boundary change
AGENTS.mdand the README both listed image scanning as deliberately out of scope. That boundary is rewritten rather than dropped: scanning is in scope as far as it stays arithmetic. Camera-grade decoding is a scanning engine and belongs in one. QR scanning stays out at a concrete cost —rqrrwould add seven crates to the published graph, so it remains a dev-dependency verifying our own output.Cost
No new dependencies — verified by diffing
cargo treewith and without the feature. Buildsno_std; added to the bare-metal CI job and the feature matrix. Roughly 0.6 ms on a real label, 165 ms worst case on 12 MP of pure noise.Tests
tests/scan.rs(16 cases) starts from the crate's own renderer and degrades the image the way reality does: quarter-turn rotation, inverted colours, transparent background, 1-pixel modules, HRI text present, pasted into a larger page with a rule across it, salt-and-pepper plus box blur, no quiet zone, and a QR code as a clean miss. Noise comes from a fixed seed so the suite cannot pass on the desk and fail in CI.cargo test --all-features,clippy -D warnings,fmt --check,doc, the no_std target, and the full feature matrix are all clean.