Camera Scanning

July 26, 2026 · View on GitHub

The Camera Scanning tab captures negatives with a tethered camera and imports them straight into NegPy — no separate capture app, no shuffling folders. It has two modes, and it picks between them on its own depending on what hardware it finds.


What it does

Normal camera scanning. One exposure of the frame under whatever light you use, imported as an ordinary RAW and processed like any other file. This needs nothing but a supported camera.

Narrowband RGB scanning. With an RGB Scanlight connected, the light flashes red, then green, then blue while the camera takes one exposure per channel. The three RAWs are handed to NegPy's existing RGB Scan merge, which sub-pixel-aligns them and assembles one frame before inversion.

Why three shots beat one: a single broadband exposure lets each dye layer contaminate the neighbouring channels, and the green Bayer filter — the broadest of the three — catches leakage from the red and blue light at once. Lighting one narrow band at a time removes that crosstalk by construction, and lets every channel use the full dynamic range of the sensor instead of sharing it.


What you need

CameraAny body libgphoto2 can drive with live view and remote capture. Bodies missing from that list often still work through the generic PTP driver — an a7C II does.
python-gphoto2An optional dependency, free software (LGPL). pip install gphoto2.
Scanlight (optional)Only needed for narrowband RGB. Without it, normal camera scanning still works.

NegPy runs perfectly well without any of this. If python-gphoto2 isn't installed, the Camera Scanning tab shows a one-line setup hint and every other part of NegPy is unaffected. Nothing proprietary is involved: libgphoto2 is LGPL, and no vendor SDK is bundled, linked or required.

⚠️ macOS and Linux only. libgphoto2 has no Windows build, so there are no Windows wheels and the tab cannot connect there.


Setup

uv sync --group camera     # or: pip install gphoto2

That is the whole install. libgphoto2 ships inside the wheel; nothing else to download, build or place anywhere. (Packaging the app yourself? Run that same command before make build — the build then bundles libgphoto2's camera drivers. Skip it and the packaged app just shows the setup hint.)

Then put the camera in PC Remote mode and plug it in over USB. It is detected automatically — there is no address to type, no login, no pairing.

On macOS, quit Preview, Photos and Image Capture first. The system's ImageCapture daemons hand a PTP camera to whichever of those apps is open, and libgphoto2 is then locked out.


Scanning

Frame and focus. Open Live View & Scan. Click anywhere on the image to aim the camera's hardware focus magnifier at that spot; click again to return to the full frame. ISO, shutter and aperture are adjustable live from the toolbar in white-light and normal (camera-only) scanning; with a calibrated RGB preset they're hidden there and locked to the preset instead (see Presets), so the scan can't drift. A control the body cannot offer — aperture on a lens with no electronic diaphragm, which is most enlarging and macro glass — is simply greyed out.

Calibrate (RGB mode). Set the ISO and aperture you'll scan with, then press + next to the preset dropdown, place the small rectangle on the clear film base — the rebate strip between frames is ideal — name the preset and run it. Calibration meters that patch and solves one shared shutter plus a per-channel LED level so each channel lands just under clipping, and records the ISO and aperture alongside them. That highlight matters: the clear base is what becomes the black point after inversion, so a clip guard checks the raw Bayer photosites and backs the exposure off if any channel saturates. Save it once per film stock and reuse it. If the target is unreachable at your exposure, the run stops right at the probe — a pop-up says which way to adjust (over-exposed → close the aperture or lower the ISO; under-exposed → open up or raise the ISO) and no preset is saved; adjust and calibrate again in the window that stayed open.

Presets. A selected preset is shown read-only — RGB levels, ISO, shutter and aperture — and the scan forces that exposure on the body before every frame, so a bumped dial can't falsify the result. To build one by hand instead of calibrating, pick Create a manual preset… from the dropdown: the sliders and exposure steppers unlock, dial them in, then press the save (floppy) button to name and store it. (White is only the white-light preset's channel — the Scanlight can't light it together with RGB.)

Scan. Pick an output folder and a preset, then press Scan for each frame. Files land in a per-roll subfolder, auto-numbered, and are imported and merged automatically — so you see the inverted positive a moment after the shutter. Retake re-shoots the current frame without advancing the counter.

Narrowband Scan. Scans lit by narrowband RGB LEDs render more saturated than white-light scans: each channel samples its dye near the absorption peak, so the natural spectral overlap of broadband light is missing. The Narrowband toggle in the Process panel (next to Linear RAW) corrects this by applying the bundled RGBScan input profile to the preview and every export. An explicit Input ICC chosen in Export settings takes precedence while set.

Sensor calibration

Single-shot scans under a narrowband RGB source can come out with hues that no slider fixes — yellows drifting orange is the classic sign. The cause is sensor crosstalk: the camera's colour-filter passbands overlap the source's bands (the green pixel sees the blue LED and some red), so every channel carries a share of its neighbours. It is a fixed property of your sensor + light pair, independent of the film.

To correct it, photograph the bare light — no film in the holder — three times: red-only, green-only, blue-only, at the same settings you scan with, exposed just below clipping. Then in the Process panel open Sensor Calibration, press the calibrate button, pick the three captures, name the profile and save. Selecting the profile un-mixes every scan with a 3×3 matrix in the linear domain, before inversion. Profiles are TOML files in the NegPy/sensor folder. Re-run Batch Analysis after changing the profile.

When not to use it: RGB-triplet (trichrome) scans are crosstalk-free by construction — each channel comes from its own single-light exposure — and NegPy skips the correction automatically for triplet assets. Dedicated film scanners (a Coolscan's mono CCD reads one LED at a time) have no sensor crosstalk either; their residual colour error is film-dye crosstalk, which the density-domain Crosstalk matrix handles (see CROSSTALK.md).


Troubleshooting

SymptomCauseFix
Camera dot shows "in use" (amber hint)An ImageCapture app is holding the body. Only one program may claim a PTP camera.Quit Preview, Photos and Image Capture. Preview is the usual culprit — it grabs the camera silently. NegPy reconnects on its own once the camera is free.
No camera found, and nothing else is runningThe body isn't in PC Remote mode, or it's a mass-storage/MTP connection.Set the camera's USB connection mode to PC Remote.
[-10] Timeout reading from or writing to the port, and no other program is holding itA program crashed while connected. The camera still thinks the session is open and refuses a new one.Power-cycle the camera, or unplug and replug the cable. Nothing on the computer will fix it.
Live view is blackThe body dropped out of PC Remote, or the lens cap is on.Power-cycle the camera.
The scan window opens with "no live view" instead of a previewlibgphoto2's entry for this body has no preview capability — either the body genuinely lacks it (Sony a6000) or it is connected in MTP mode, where no body has it.If the message names MTP, set the camera's USB mode to PC Remote and reconnect. Otherwise it is expected: scanning works normally, but framing and focus have to be set on the camera, and calibration is unavailable because it aims through the live view.
Capture says the camera returned JPEG instead of RAWThe camera's image-quality setting is JPEG or RAW+JPEG selected the processed file.Set image quality to RAW only, then retry.
The aperture stepper is greyed outThe lens has no electronic diaphragm.Expected — set the aperture on the lens itself.
A setting snaps back to its old valueProperty writes are asynchronous; the body needs a moment.NegPy already polls until the value lands and logs a warning if it never does. If it never does, that setting is not writable in the body's current mode (try M).
Scanlight not detectedWrong USB-C port.The Scanlight has two: only one is the data port (the other is power-only). Use the data port.

Notes and limitations

  • USB only. libgphoto2 can reach some cameras over the network, but not Sony bodies; the tab is a tethered-USB workflow.
  • Only Sony bodies are tested — that is the hardware on hand. Nothing in NegPy assumes a vendor, though: every control is looked up rather than assumed. iso and shutterspeed are named the same everywhere; the aperture is f-number on Sony and Panasonic but aperture on Canon, Nikon, Fujifilm, Olympus and Sigma. The RAW suffix comes from the camera, and the still is taken into memory rather than onto a card (Canon and Nikon default to the card, and will not shoot without one). Reports from other brands are very welcome.
  • The focus magnifier depends on the vendor. Sony packs the zoom ratio and the target point into one property, so a click both magnifies and aims. Canon (eoszoom) and Nikon (liveviewimagezoomratio) split them, and their coordinate space is unknown here, so a click magnifies where the body already looks. Every other body has no magnifier at all, and the feature disables itself.
  • Tested on macOS. The Python is portable and libgphoto2 is a Linux-first project, so Linux should be at least as good — but it is unverified.
  • Speed. A three-shot RGB triplet takes roughly six seconds on an a7C II over USB. Almost all of that is the body's per-image transfer latency, not the file size. Stills are taken inside the running live-view session, so there is no per-frame reconnect.
  • Credit. The R/G/B sequencing and exposure-calibration approach come from rohanpandula/TriRGB; the light is jackw01/scanlight; the camera is driven through python-gphoto2. The narrowband approach follows Flückiger et al.'s work on trichromatic film scanning.