stitch.py and pagegraph.py mixed laser odometry, image registration,
frame correction, canvas handling, pose-graph maths and the two stitchers.
They are now modules of myscan/stitching, which imports nothing from the
device code:
geometry rigid transforms, pose vectors, warp_add
filters grayscale, high-pass, feather weights
correct Corrector: undistortion + flat field (and weighted variant)
odometry LaserOdometry: the laser model and the phase-correlation
override, shared by both stitchers
register ECC, phase correlation, SIFT matching, overlap agreement
canvas growable Canvas, layout, parallel render, normalize/compose
posegraph robust Gauss-Newton, largest connected component
loops keyframes and loop-closure candidates
pagegraph PageGraph, now orchestration only
incremental Stitcher, on the shared Canvas
The stitchers take a Corrector and an odometry object (anything with
predict()) instead of building them, so another device can supply its
own motion prior. live.py drops its own canvas and normalisation for the
shared ones; PageGraph's mosaic check uses the parallel renderer.
Output on scan_text is unchanged (global stitcher identical, incremental
within 1 grey level). stitch.py --live now applies lens correction too
(--no-undistort restores the old output).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frame-by-frame stitcher accumulated drift, so later passes landed rotated
and offset, doubling text. myscan/pagegraph.py instead solves all frame poses
together: ECC odometry between consecutive frames (bridging single corrupt
frames), SIFT+RANSAC loop closures between overlapping passes, and a robust
Gauss-Newton pose graph. Loop matches must agree over the whole overlap, which
rejects matches on repeated text. Off-page frames and frames that disagree
with the mosaic are left out of the render.
calib.Undistort applies the EEPROM lens model (OpenCV k1..k3, p1, p2 plus
the tilt homography, as decoded in EEPROM.md); up to 8.8 px at the corners.
scan.py registers frames on a worker thread so capture no longer drops
frames, then assembles the page after the stop press (~11 s for a full
page). stitch.py uses the new assembly by default; --live keeps the old one.
EEPROM.md documents the full calibration blob layout, checksum and signature.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ScanSession: scan-mode context manager with bright-pixel auto-exposure and
sensor colour gains; tools/scan.py scans on the scan button with live stitching
- Read-only EEPROM access and calib.py parser (laser geometry, intrinsics,
colour gains, black/white flat-field samples); flat-field correction in Stitcher
- OV7740 sensor access via the MCU proxy; colour gains from EEPROM fix green cast
- Stitcher: colour support, high-passed registration (robust on blank paper)
- RE notes on the Mac app's colour, exposure, stitching and button handling
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UVC extension-unit control (mouse/scan mode, LEDs, exposure), JPEG header
fix and embedded laser record decoding, session recorder, and a stitcher
that combines laser dead reckoning with phase correlation and ECC refinement.
FINDINGS.md holds the reverse-engineered protocol and laser calibration.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>