Files
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

62 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""Scan with the scan button, like the original app: press it to start, glide the
mouse over the page, press it again to stop. Frames are registered while you scan;
after the stop press the page is assembled from all of them at once (see
myscan/stitching/pagegraph.py) and saved as <outdir>/scan-YYYYmmdd-HHMMSS.png. The mouse goes
back to mouse mode between scans (and on Ctrl-C). The loop itself is myscan/scanner.py.
With --view, a browser viewer (myscan/webview.py) shows the camera and the page as it
is being stitched, and has Start/Stop buttons that work like the scan button."""
import argparse
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from myscan.session import quiet_libjpeg # noqa: E402
quiet_libjpeg()
from myscan.scanner import Scanner # noqa: E402
def main():
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--outdir", default="scans")
ap.add_argument("--once", action="store_true", help="exit after one page")
ap.add_argument("--now", action="store_true", help="start the first scan immediately")
ap.add_argument("--video", help="capture device (default: find the myScan, else /dev/video4)")
ap.add_argument("--target", type=float, default=200, help="auto-exposure target for paper (95th pct of brightest channel)")
ap.add_argument("--exposure", type=int, help="fixed E6 value instead of auto-exposure")
ap.add_argument("--gray", action="store_true")
ap.add_argument("--no-wb", action="store_true", help="skip white-patch balancing")
ap.add_argument("--no-flat", action="store_true", help="skip the EEPROM flat-field correction")
ap.add_argument("--no-undistort", action="store_true", help="skip the EEPROM lens correction")
ap.add_argument("--save-frames", action="store_true", help="also keep raw frames + records.csv per scan")
ap.add_argument("--max-time", type=float, default=180, help="stop a scan after this many seconds")
ap.add_argument("--view", type=int, nargs="?", const=8000, metavar="PORT",
help="serve a live browser view on PORT (default 8000)")
a = ap.parse_args()
viewer = None
if a.view is not None:
from myscan.webview import Viewer
viewer = Viewer(a.view)
print("live view: " + " ".join(viewer.urls()), flush=True)
scanner = Scanner(a, viewer)
first = True
try:
while True:
if not (first and a.now):
print("press the scan button to start (Ctrl-C to quit)", flush=True)
scanner.wait_press()
first = False
scanner.scan()
if a.once:
break
except KeyboardInterrupt:
print("\nbye")
if __name__ == "__main__":
main()