Files
myscan/tools/record.py
T
erikandClaude Opus 5.5 e70d56d957 Live button-driven scanning with EEPROM calibration
- 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>
2026-10-02 11:13:59 +00:00

62 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""Record a scan session: scan mode + auto-exposure, saving every frame (fixed
640x296 colour PNG) and its embedded sensor record to records.csv.
Mouse mode is restored on exit (also on Ctrl-C)."""
import argparse
import csv
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from myscan import calib # noqa: E402
from myscan.session import ScanSession, quiet_libjpeg # noqa: E402
quiet_libjpeg()
import cv2 # noqa: E402
def main():
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--duration", type=float, default=20)
ap.add_argument("-o", "--out", default="scan")
ap.add_argument("--video", default="/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", help="save grayscale frames")
a = ap.parse_args()
os.makedirs(a.out, exist_ok=True)
rows = []
s = ScanSession(a.video, a.target, a.exposure, gains=calib.load()["gains_rgb"])
try:
with s:
print(f"scanning for {a.duration:.0f}s -> {a.out}/ (Ctrl-C to stop early)", flush=True)
last = 0.0
for t, n, im, rec in s.frames(color=not a.gray):
if t >= a.duration:
break
cv2.imwrite(f"{a.out}/f{n:05d}.png", im)
mean = float(im.mean())
rows.append([round(t, 4), n, rec.counter, *rec.v, rec.b14, rec.b15, rec.b16, rec.flags, s.e6,
round(mean, 1), rec.raw.hex()])
if t - last >= 1:
print(f"t={t:5.1f}s frames={n:4d} E6={s.e6:3d} bright={mean:5.1f} "
f"l1=({rec.v[0]:#06x},{rec.v[1]:#06x}) l2=({rec.v[3]:#06x},{rec.v[4]:#06x}) "
f"sq={rec.b14},{rec.b15} flags={rec.flags:05b}", flush=True)
last = t
except KeyboardInterrupt:
pass
finally:
print("restore mouse mode:", getattr(s, "restored", None))
with open(f"{a.out}/records.csv", "w", newline="") as fh:
w = csv.writer(fh)
w.writerow(["t", "frame", "counter", "v0", "v1", "v2", "v3", "v4", "v5",
"b14", "b15", "b16", "flags", "e6", "brightness", "raw"])
w.writerows(rows)
print(f"{len(rows)} frames saved to {a.out}/")
if __name__ == "__main__":
main()