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>
This commit is contained in:
+59
@@ -343,3 +343,62 @@ handling**. There is nothing in it to build a Linux driver from.
|
||||
frame-to-frame phase correlation, then ECC-refines every frame against the canvas.
|
||||
- Frames are transposed + mirrored relative to the page; output = flip(transpose(canvas), -1).
|
||||
- `tools/stitch.py <dir>` -> <dir>/page.png. Full 751-frame session stitches cleanly.
|
||||
|
||||
## Per-device calibration in EEPROM (read 2026-10-02)
|
||||
|
||||
Read-only access is `XU.eeprom_read(addr, n)`, XU selector 3:
|
||||
- `01 addr len`: set address and length.
|
||||
- `02`: start the read.
|
||||
- `03`: returns the next 32 bytes; repeat until done.
|
||||
- `05`: end.
|
||||
|
||||
The write path uses commands 06/07/09 and is RSA-signed. It is not implemented.
|
||||
|
||||
Layout:
|
||||
- `0x3000`: u16 blob length (19417 on this unit).
|
||||
- `0x3002`: checksum.
|
||||
- `0x3006`: a boost binary archive (i386 sizes).
|
||||
- `0x7EB5`: 64-byte ID.
|
||||
|
||||
`myscan/calib.py` parses it. Values on this unit:
|
||||
|
||||
| Field | Value |
|
||||
|---|---|
|
||||
| ROI | 0,0 – 640,295 |
|
||||
| Laser scale (left, right) | 1.922e-5, 1.878e-5 m/count |
|
||||
| Laser positions (camera frame) | (11.25, 53.8) mm and (33.55, −0.67) mm |
|
||||
| Intrinsics | f = 737.3 px, principal point (317.5, 132.7); 5 distortion coefficients |
|
||||
| `pixelSize` | 61.3 µm |
|
||||
| `gainR/G/B` | 115 / 89 / 64 (sensor registers 1/2/3) |
|
||||
|
||||
Colour calibration is stored as point samples, not as NURBS coefficients:
|
||||
- 827 black samples and 873 white samples.
|
||||
- Each sample is `u16 x, u16 y, u32 3, u8 b, g, r` in raw-frame coordinates.
|
||||
- The white surface matches the illumination falloff of real frames (correlation 0.91–0.95 per channel).
|
||||
|
||||
`FlatField` fits cubic B-spline surfaces to the samples. Its default correction divides each frame by the normalised white surface. The app's full `(in − black)/(white − black)·250` is only valid at factory exposure and gains.
|
||||
|
||||
Auto-exposure now regulates the 95th percentile of the brightest channel (target 200). The old mean-brightness target let green clip at about 233, which broke the linear correction.
|
||||
|
||||
### Image sensor access and colour gains
|
||||
|
||||
The image sensor is an **OV7740**: PID 0x77/0x42, MID 0x7F/0xA2.
|
||||
|
||||
Two access paths work, both only in scan mode (after D8 E1→99):
|
||||
- **MCU proxy**, which is what the app uses:
|
||||
- Read: poll until B8=0, write BD=reg, write B8=2, poll until B8=0, then read BE.
|
||||
- Write: write BD=reg, BE=val, B8=1, then read back to verify.
|
||||
- **Bridge path** (UVCControl `ReadSCCB`/`WriteSCCB`): set F1=0x42, then XU selector 2 command 0x81/0x01. The app never uses it.
|
||||
|
||||
In mouse mode B8 stays stuck at 2. In standby BE reads 0.
|
||||
|
||||
Colour gains: firmware leaves regs 1/2/3 (blue/red/green, 0x40 = 1×) at 64/64/64. The app writes the EEPROM's `gainR/G/B` = 115/89/64 to regs 1/2/3 in that order.
|
||||
|
||||
Measured frame B:G:R:
|
||||
|
||||
| Gains | B | G | R |
|
||||
|---|---|---|---|
|
||||
| Firmware default | 6.5 | 14.3 | 10.8 |
|
||||
| EEPROM gains | 13.0 | 13.4 | 13.3 |
|
||||
|
||||
The EEPROM gains remove the green cast in hardware. `ScanSession(gains=...)` applies them; `scan.py` and `record.py` pass the EEPROM values.
|
||||
|
||||
Reference in New Issue
Block a user