Canon scanners
Two desktop apps that turn a Canon EOS R7 into a film scanner, one over USB at 59.79 fps and one over Wi-Fi at 3.98.
Two apps do the same job: scan a film negative with a Canon EOS R7 on a copy stand. One drives the camera over USB with EDSDK, the other over Wi-Fi with CCAPI. Both invert the negative live, so you work in positive, push focus from the keyboard, and fire the shutter without touching the rig.
I wrote the Wi-Fi one first and measured it at 3.98 fps at 960x640. CCAPI has no USB transport and the R7 is Wi-Fi 4, so there was nothing left to tune. I ported the app to EDSDK and got 59.79 fps on the same body at the same resolution, a 15x gain, with the camera’s own output rate the remaining limit rather than the link. The inversion is one code path for the preview and the saved file, because every step is a scalar function of one input and collapses into a lookup table.
The packaged build runs by double-clicking, starts on a mock camera so there is something to look at, and refuses to finish if any of Canon’s files reach the output. What is not built is in the issues: corner alignment, the cyan colour preview on the Wi-Fi build, and an unsigned binary.
- 1 59.79 fps over USB EDSDK live view at 960x640, against 3.98 fps on the Wi-Fi build.
- 2 One inversion path Every step is a scalar of one input, so the preview and the saved file cannot drift.
- 3 A bundled mock camera The build runs with no Python, no SDK and no hardware attached.
- 4 Fine focus, 1x1 The SDK minimum, chosen by hand at 8x magnification, not by the frame metric.
Overview
What I built
- +Two apps for one job: EDSDK at 59.79 fps over USB, CCAPI at 3.98 fps over Wi-Fi, 15x the frame rate.
- +A double-clickable build of 66 MB with a mock camera, and 250 tests that need neither a camera nor the SDK.
- +Deflate TIFF positives at 2.4 to 2.6x, verified lossless by reading every pixel back.
What I rebuilt
- ~Dropped OpenCV for Pillow, tifffile and numpy, taking the packaged app from 176 MB to 66 MB.
- ~Stopped reading Canon HEIF as sRGB and read the PQ transfer characteristic out of the file instead.
- ~Gave the SDK the main thread on macOS, because EdsGetCameraList returns zero cameras from anywhere else.
- ~Cut fine focus to the SDK minimum of one step by one after the whole-frame metric stopped resolving it.
Known limitations
- !Corner-by-corner alignment is not built; the per-region sharpness it needs is.
- !The CCAPI build still previews colour negatives cyan, because its inversion is the first linear flip.
- !Packaged builds are unsigned, so Gatekeeper blocks them on any other Mac.
Decisions
- 1. Change SDK, do not tune Wi-Fi I chose port the app to EDSDK, which does have a USB transport, Instead of optimising the CCAPI live view loop further, Because CCAPI has no USB path and the R7 is Wi-Fi 4 on 2.4 GHz, so the link itself was the ceiling at 251 ms a frame..
- 2. Read PQ from the file I chose read the NCLX transfer characteristic and convert through the same lookup table, Instead of decoding Canon HEIF as sRGB, Because decoded as sRGB it was 3.1% mean error against 0.9% on a real negative's range, and 31x across a 30x range..
- 3. Replace OpenCV, do not tune it I chose Pillow for JPEG, tifffile for 16-bit TIFF, numpy for gradients and adds, Instead of keeping OpenCV for its JPEG coding, resize, Scharr gradient and saturating add, Because its wheel carried about 49 MB of GPL video codecs for four small operations, and it writes BGR where the others write RGB..
Timeline
| Version | Date | Description |
|---|---|---|
| v0.1.0 | 2026-08 | CCAPI build: live view, remote capture, then focus drive, peaking and a sharpness readout. |
| v0.2.0 | 2026-08 | EDSDK measured at 59.79 fps against 3.98 on Wi-Fi, and the port followed. |
| v0.3.0 | 2026-08 | Film inversion, HEIF developed as PQ, capture settings, and rolls with a naming template. |
| v0.4.0 | 2026-08 | macOS support, the SDK moved onto the main thread, and a packaged double-clickable app. |
| v1.0.0 | 2026-08 | OpenCV dropped and notices shipped, then a 25-check walkthrough on the real R7 passed 25 of 25. |