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>
49 lines
4.0 KiB
Markdown
49 lines
4.0 KiB
Markdown
# myscan
|
|
|
|
Linux driver tools for the Hama myScan (00052343) scanner mouse, a Dacuda design
|
|
(USB 274f:c001: UVC MJPEG camera + HID mouse).
|
|
|
|
- `myscan/xu.py` — UVC extension-unit access: switch between mouse and scan mode, LEDs, exposure
|
|
- `myscan/frame.py` — fix the lying JPEG header and decode the per-frame sensor record (laser dx/dy)
|
|
- `myscan/calib.py` — per-device calibration from the mouse's EEPROM: colour gains, flat field, lens undistortion
|
|
- `myscan/stitching/` — page stitching, independent of the scanner hardware (no device imports), so it can
|
|
be used elsewhere: `from myscan.stitching import Corrector, PageGraph`. Its modules, lowest level first:
|
|
`geometry` (rigid transforms, poses), `filters` (grayscale, high-pass, feather weights), `correct`
|
|
(`Corrector`: lens undistortion + flat field), `odometry` (`LaserOdometry`, the myScan's laser motion
|
|
prior; another device can pass its own object with `predict()`), `register` (ECC, phase correlation, SIFT
|
|
matching, overlap check), `canvas` (weighted canvases, page composition, parallel rendering), `posegraph`
|
|
(robust pose-graph optimisation), `loops` (loop-closure candidates), and the two stitchers built from them:
|
|
`pagegraph` (`PageGraph`: assembles the page from all frames at once with loop closures between passes;
|
|
used by `scan.py` and `stitch.py`) and `incremental` (`Stitcher`: frame by frame, `stitch.py --live`)
|
|
- `tools/scan.py` — scan with the button: `python tools/scan.py` (press to start, press again to stop).
|
|
`--view [PORT]` serves a live view at `http://<host>:8000/`: the camera, the page as it is stitched
|
|
(re-solved every 1.5 s, current position outlined), Start/Stop buttons, PNG/JPEG download of the finished
|
|
page and a Screenshot button (camera + page + status as one PNG)
|
|
- `tools/app.py` — desktop app (Qt/PySide6; Linux, Windows, macOS): live camera and page view while scanning
|
|
(Start/Stop or the scan button, F5), then OCR of the finished page with RapidOCR (`myscan/ocr.py`;
|
|
multilingual PP-OCRv6 models bundled in the wheel, works offline). Recognised lines are outlined on the page by
|
|
confidence, and the text is selected right on the page like in a PDF viewer: drag (in reading order across
|
|
lines), double-click a word, triple-click a line, shift-click to extend, Ctrl+A; Ctrl+C or right-click copies.
|
|
With Paragraphs on (Ctrl+J, the default), copied and saved text has each paragraph on one line:
|
|
line breaks inside a paragraph become spaces and words hyphenated across lines are rejoined; a line ends a
|
|
paragraph if the next line is indented or its first word would have fitted on it (works for justified and
|
|
ragged text), and rows with several columns are left alone. Right-click offers the other form. Middle-drag or
|
|
Space+drag pans, the wheel zooms. Copy all and Save text (.txt) take the whole page. `python tools/app.py [image]` also opens and reads existing images (Open, or
|
|
drag and drop), which is all it does on machines without the mouse
|
|
- `myscan/scanner.py` — the scan loop shared by `scan.py` and the app (finds the mouse's `/dev/videoN` itself)
|
|
- `myscan/live.py`, `myscan/webview.py` — the live preview and its browser viewer (standard library only)
|
|
- `tools/record.py` — record a scan session: `python tools/record.py -d 25 -o scan_text`
|
|
- `tools/stitch.py` — stitch a recording: `python tools/stitch.py scan_text` -> `scan_text/page.png`
|
|
- `tools/probe.py` — log raw frames and HID reports side by side
|
|
- `tools/scantest.py` — minimal scan-mode test: grab frames, decode records, restore mouse mode
|
|
|
|
See [FINDINGS.md](FINDINGS.md) for the protocol and calibration notes, and [EEPROM.md](EEPROM.md) for the
|
|
calibration blob.
|
|
|
|
Setup: `python -m venv .venv && .venv/bin/pip install -r requirements.txt`
|
|
|
|
**Calibration cache:** `calib/eeprom_3000.bin` in this repo is the EEPROM dump of one specific mouse.
|
|
`myscan/calib.py` uses that file whenever it exists and only reads the mouse when it is missing. With a
|
|
different mouse, delete it first (`rm calib/eeprom_3000.bin`), so the next scan reads and caches your own
|
|
unit's calibration (about 15 s).
|