12 Commits
Author SHA1 Message Date
erikandClaude Opus 5.5 04c10e58c7 Drop frames taken while the mouse lies still
PageGraph.add skips a frame when neither the laser odometry nor a small
thumbnail shows it moved since the last kept frame, so resting on the page
no longer costs registration, solving and rendering time. LiveSession maps
captures to the kept frames for its sensor readings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 08:22:27 +00:00
erikandClaude Opus 4.7 0dd807a4e7 Add a sensor dial under the camera with live scan readings
Shows the mouse's rotation on the page since the scan started (a big "A",
coloured by tracking), direction and speed (arrow), and heading, speed,
distance, offset, area, tracking, laser quality, exposure, paper level
and fps. Also drops the "Read text", "Auto" and "Save text" toolbar
actions (OCR runs automatically on every finished page or opened image).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-10-11 21:44:03 +00:00
erikandClaude Opus 5.5 16df509d01 Level the page view by the text direction found by OCR
The page, its line boxes and the selection now hang under one rotated item, so
selection works the same whether the page is levelled or shown as stitched.
Level (Ctrl+L, on by default) toggles it, and Save image saves what is shown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:06:59 +00:00
erikandClaude Opus 5.5 2ef567580a Decide text orientation per page instead of per line
RapidOCR's angle classifier judges each cut-out line on its own and now
and then turns a perfectly upright line upside down, which then reads as
garbage ("Jo a ds ne t i d s t d no" for a line ending in "or" on the
latest scan). It is off now. All lines on a page face the same way, so a
page that reads poorly (upright text scores 0.97-1.0 per character,
upside-down text 0.55-0.8) is read again rotated 180 degrees and the
better reading kept; that also covers pages turned 90 degrees. The app
shows an upside-down page turned the right way up.

Upright pages read about 25% faster without the per-line classifier.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:44:40 +00:00
erikandClaude Opus 5.5 6631073be5 Copy OCR text as paragraphs
A Paragraphs toggle (Ctrl+J, on by default) makes copied and saved text
put each paragraph on one line: line breaks inside a paragraph become
spaces and words hyphenated across lines are rejoined (the hyphen stays
before a capital, as in "Nord-Amerika"). A line ends its paragraph if the
next one is indented, has a different text size, or starts with a word
that would have fitted at the end of this one, which works for justified
and ragged-right text alike; rows of several items (tables) are left
alone. The right-click menu offers the other form.

ocr.paragraph_joins gives the joins as edits on layout()'s text, and
reflow applies those inside any range, so selections keep working on the
page's character positions.

Ctrl+C now copies in the chosen form (QAction.triggered passed
checked=False through as paragraphs=False), and tabs show in the status
bar's selection preview.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:44:40 +00:00
erikandClaude Opus 5.5 67333528ca Select and copy text directly on the page
The text pane is gone: recognised text is selected on the page image like
in a PDF viewer (drag in reading order, double-click a word, triple-click a
line, shift-click to extend, Ctrl+A), copied with Ctrl+C or the context
menu, and previewed in the status bar; on X11/Wayland it also goes to the
primary selection. Middle-drag or Space+drag pans.

OCR asks RapidOCR for per-character boxes and keeps each line's character
boundaries (Line.edges), with Line.locate/quad/word for hit testing and
drawing; lines whose boxes don't match the text fall back to even spacing.

Errors are logged to the terminal with their traceback, and Ctrl+C in the
terminal closes the app (restoring mouse mode), dismissing any open dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:25:11 +00:00
erikandClaude Opus 5.5 909baec1e7 Desktop app with live scanning and OCR
tools/app.py (myscan/app.py, Qt/PySide6) shows the camera and the page as it
is stitched, Start/Stop or the scan button (F5), then reads the finished page
with RapidOCR (myscan/ocr.py; bundled multilingual models, offline). Lines are
outlined on the page by confidence and linked to an editable text pane that
can be copied or saved. Existing images can be opened or dropped, so the app
is useful without the mouse.

The scan loop moves from tools/scan.py to myscan/scanner.py, shared by both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:05:22 +00:00
erikandClaude Opus 5.5 c8b9f610bf Split stitching into a hardware-independent myscan.stitching package
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>
2026-10-09 07:55:22 +00:00
erikandClaude Opus 5.5 f47df93c97 Live browser view while scanning, with downloads and screenshots
scan.py --view serves a page (myscan/webview.py, standard library only) with
MJPEG streams of the latest camera frame and the page as it is stitched,
Start/Stop buttons, PNG/JPEG download of the finished page and a screenshot
of the current view.

myscan/live.py keeps a downscaled preview: new frames are pasted at their
current best pose, and a background thread re-solves the pose graph every
1.5 s (throttled to at most ~50% duty) and redraws the preview off the
registration thread. PageGraph.solve can now run on a prefix of the frames
while add() continues, keeping loop closures and features between solves,
so the final solve after the stop press is much shorter. Frame PNGs are
saved on their own thread.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:51:59 +00:00
erikandClaude Opus 5.5 a0ae1ffc0d README: note that the committed EEPROM dump is one unit's calibration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:08:04 +00:00
erikandClaude Opus 5.5 026aa6c894 Global page assembly with loop closures and lens undistortion
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>
2026-10-02 13:06:45 +00:00
erikandClaude Opus 5.5 e1c1012c1c Hama myScan Linux driver tools: scan mode, frame decoding, stitching
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>
2026-10-02 10:41:22 +00:00