Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
247db85
Lento Dock: start the scan at the S press
cimelab Oct 30, 2026
2a8c73a
fix(transparency): split a camera RAW's matrix at the white-balance f…
thetalkingdrum Nov 1, 2026
e5080dd
refactor(transparency): fold camera white-balance gains into the dens…
thetalkingdrum Nov 1, 2026
36b4ff8
fix(fade): scale a camera RAW's density from its own white
thetalkingdrum Nov 1, 2026
979dcac
fix(fade): name the white's channel space in its tag
thetalkingdrum Nov 1, 2026
3e1decb
fix(fade): scale a camera RAW's density about a neutral level at its …
thetalkingdrum Nov 2, 2026
18633ea
fix(fade): light past the film passes the fade as a neutral
thetalkingdrum Nov 2, 2026
74254b4
fix(fade): keep the fade name out of the negative package
thetalkingdrum Nov 2, 2026
1cc856c
fix(fade): cap highlights at the white on a camera RAW only
thetalkingdrum Nov 2, 2026
2c83c6a
fix(rolls): keep a camera RAW's fade white on its frame
thetalkingdrum Nov 2, 2026
ff9b0b2
fix(fade): name the white's space by the white-balance gains only
thetalkingdrum Nov 2, 2026
f7d33cc
fix(fade): clear the local bounds when a stale white is measured again
thetalkingdrum Nov 2, 2026
af0cd29
fix(fade): store a failed Estimate's camera RAW white without a roll …
thetalkingdrum Nov 2, 2026
ceed1f4
fix(fade): measure a stale camera RAW white in the render
thetalkingdrum Nov 2, 2026
a0ede09
fix(rolls): keep a camera RAW frame's own fade white against the roll
thetalkingdrum Nov 2, 2026
2de7c0d
fix(fade): store the render's white with its bounds and tag the cross…
thetalkingdrum Nov 2, 2026
41d1eb0
perf(core): run relog_matrix in runs that vectorize
thetalkingdrum Nov 2, 2026
e6189ab
perf(fade): add the density offset in the anchored kernel and share t…
thetalkingdrum Nov 2, 2026
3fe3802
Merge remote-tracking branch 'upstream/main' into claude/raw-density-…
thetalkingdrum Nov 2, 2026
89e6bd9
fix(fade): keep a camera RAW frame's own white on Paste, Apply and pr…
thetalkingdrum Nov 2, 2026
2e6801d
fix(fade): name Highlight Reconstruction in the white's tag
thetalkingdrum Nov 2, 2026
21db3ba
fix(rolls): lock a scanner frame's Fade card on its own white
thetalkingdrum Nov 2, 2026
b651b66
fix(rolls): keep a roll's scanner white when a camera RAW pushes the …
thetalkingdrum Nov 2, 2026
367ce56
refactor(fade): stop zeroing the stored white for the Estimate grid
thetalkingdrum Nov 2, 2026
af134ed
Blend capture: R, G and B in turn inside one Bulb exposure
cimelab Nov 2, 2026
5b72de9
fix(scanners): detect SANE film scanners by their device type
packetscrub Nov 3, 2026
0c1f718
fix(scanners): set the color depth by mode name on avision
packetscrub Nov 3, 2026
eb5b7e5
Lento Dock: follow the published serial API
cimelab Nov 3, 2026
b0c60ee
fix(fade): stop a camera RAW's white at full scale
thetalkingdrum Nov 3, 2026
8554053
feat(files): add Frame No. sort and contact-sheet order
red-prs Nov 4, 2026
f4ea53c
Merge remote-tracking branch 'upstream/main' into claude/raw-density-…
thetalkingdrum Nov 4, 2026
6992d47
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
c94ab9e
Lento Dock: take SCANREQ as the S button
cimelab Nov 4, 2026
caf8c5f
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
d5c19ef
Lento Dock: let the dock align a new strip on its own
cimelab Nov 4, 2026
9fad22c
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
813300f
Lento Dock: log the dock's state changes and S presses
cimelab Nov 4, 2026
51b93d3
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
d900f33
Open a fresh Blend frame as its capture imported it
cimelab Nov 4, 2026
6959939
Lento Dock: read the S button with BTN?
cimelab Nov 4, 2026
568b51b
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
0364e09
Lento Dock: the arrow buttons jog while NegPy holds the dock
cimelab Nov 4, 2026
f9c9703
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
c2ed272
Lento Dock: keep the dock awake, and reconnect a silent one
cimelab Nov 4, 2026
e5a1f07
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
0f1d639
Offer the white-light presets with the Lento Dock
cimelab Nov 4, 2026
46f17c0
Merge feat/lento-dock-light into feat/lento-dock-advance
cimelab Nov 4, 2026
cdaf8c8
Merge feat/lento-dock-advance into feature/bulb-blend-capture
cimelab Nov 4, 2026
56c418a
Merge remote-tracking branch 'upstream/main' into claude/pr-1596-merg…
thetalkingdrum Nov 4, 2026
933aedd
feat(i18n): add Norwegian Bokmål translation
jboneng Nov 4, 2026
b6b4d36
Format the Blend tests
cimelab Nov 4, 2026
b908a03
Re-run CI
jboneng Nov 4, 2026
eddd5d1
fix(optics): keep camera Optics off new film scanner frames
kurtwindisch Nov 5, 2026
ff9b0b9
fix(raw-decode): keep carried Recovery off new film scanner frames
kurtwindisch Nov 5, 2026
7bb3001
fix(optics): stop camera settings at the source for film scanner frames
kurtwindisch Nov 5, 2026
6a5495f
feat(perf): size the Preferences memory limits to the machine
jondestoppeleire Nov 4, 2026
d9e7383
Merge pull request #1599 from packetscrub/fix/sane-film-scanner-devic…
marcinz606 Nov 5, 2026
82b570e
Merge branch 'main' into claude/raw-density-order
marcinz606 Nov 5, 2026
d65cbff
Merge pull request #1596 from thetalkingdrum/claude/raw-density-order
marcinz606 Nov 5, 2026
e8f020b
Merge pull request #1610 from kurtwindisch/fix/film-scanner-optics
marcinz606 Nov 5, 2026
8586351
Merge pull request #1598 from jondestoppeleire/feat/hardware-memory-b…
marcinz606 Nov 5, 2026
88dad3e
Merge pull request #1608 from jboneng/feat/i18n-norwegian-bokmal
marcinz606 Nov 5, 2026
1a8bed0
Merge commit 'refs/prs/1605' into mz/fno
marcinz606 Nov 5, 2026
ea6934c
Fill the Norwegian catalog for the memory limits and film scanner optics
marcinz606 Nov 5, 2026
1eebd21
Merge branch 'mz/nbfix' into mz/fno
marcinz606 Nov 5, 2026
22760bc
Fill the vi and nb catalogs for Frame No. sort, trim the Film Strip g…
marcinz606 Nov 5, 2026
81780b1
Merge pull request #1612 from marcinz606/mz/nbfix
marcinz606 Nov 5, 2026
792f3e1
Merge remote-tracking branch 'origin/main' into mz/fno
marcinz606 Nov 5, 2026
d7b668c
Merge pull request #1605 from red-prs/claude/roll-sort-frame-no
marcinz606 Nov 5, 2026
d1a342b
Merge main into feature/bulb-blend-capture
marcinz606 Nov 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

[![Platform](https://img.shields.io/badge/platform-linux%20%7C%20macos%20%7C%20windows-lightgrey)](#getting-started)
[![Film](https://img.shields.io/badge/film-C41%20%7C%20B%26W%20%7C%20E6-red?logo=googlephotos&logoColor=white)](docs/PIPELINE.md)
[![Languages](https://img.shields.io/badge/languages-EN%20%7C%20日本語%20%7C%20한국어%20%7C%20中文%20%7C%20DE%20%7C%20VI-informational)](docs/USER_GUIDE.md)
[![Languages](https://img.shields.io/badge/languages-EN%20%7C%20日本語%20%7C%20한국어%20%7C%20中文%20%7C%20DE%20%7C%20VI%20%7C%20NB-informational)](docs/USER_GUIDE.md)

[![Python](https://img.shields.io/badge/python-3.13%2B-blue)](pyproject.toml)
[![Rust](https://img.shields.io/badge/rust-negpy__core-orange?logo=rust)](negpy_core)
Expand All @@ -30,7 +30,7 @@ The [User Guide](docs/USER_GUIDE.md) covers every panel and control (the ⓘ on

## Languages

The desktop interface supports English, Simplified Chinese (简体中文), Japanese (日本語), Korean (한국어), German (Deutsch) and Vietnamese (Tiếng Việt). The panel guides are translated into all of them except Vietnamese. Choose a language in **Preferences → Interface → Language**, then restart NegPy. **System Default** follows your system language and uses English for unsupported languages. Missing panel translations fall back to English. See [CONTRIBUTING.md](CONTRIBUTING.md#translations) to update or add a translation.
The desktop interface supports English, Simplified Chinese (简体中文), Japanese (日本語), Korean (한국어), German (Deutsch), Vietnamese (Tiếng Việt) and Norwegian Bokmål (Norsk bokmål). The panel guides are translated into all of them except Vietnamese. Choose a language in **Preferences → Interface → Language**, then restart NegPy. **System Default** follows your system language and uses English for unsupported languages. Missing panel translations fall back to English. See [CONTRIBUTING.md](CONTRIBUTING.md#translations) to update or add a translation.

## Features

Expand Down
93 changes: 80 additions & 13 deletions docs/CAMERA_SCANNING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ together for one exposure: one shutter actuation per frame and no registration.
imports as an ordinary RAW, and its roll is recorded with Trichrome Mode off. The channels
overlap on the sensor, so pair it with a [sensor calibration](#sensor-calibration) profile.

**Blend scanning.** A Blend preset shoots one Bulb exposure per frame. The
shutter opens in the dark, the light shows red, then green, then blue, and the shutter
closes in the dark. Each channel gets the light of its Trichrome exposure, so the frame
needs no registration and has no channel offset. It imports as one RAW, as a Single
Exposure frame does. Each LED still reaches every sensor channel, so Blend needs a
[sensor calibration](#sensor-calibration) profile. See [Blend](#blend).

## What you need

| | |
Expand Down Expand Up @@ -140,7 +147,7 @@ aperture. The scan sets that exposure on the body before every frame. The dropdo
own presets and the built-ins you have added from **+** › **Built-in Presets**; a built-in stays
in the list until the trash removes it. To build a preset by hand, press **+** beside
PRESET and pick **Set by Hand** to unlock the sliders and the exposure steppers. Set them and **Capture
mode** (Trichrome or Single Exposure); Scan uses them as they stand, and the save (floppy)
mode** (Trichrome, Single Exposure or Blend); Scan uses them as they stand, and the save (floppy)
button keeps them as a preset. Only the
white-light presets use the White channel, because the Scanlight cannot light it together with the red, green and blue LEDs. The white-light presets differ only in film type: AUTO classifies each capture,
**B&W Negative**, **Color Negative** and **Slide** fix it, for the import and for Meter. **White
Expand All @@ -149,8 +156,50 @@ need no calibration either: full LED power at the camera's shutter, which Meter
a **+ IR** preset ends each frame with an infrared exposure at **IR Shutter**. The
**Infrared** toggle beside IR Shutter does the same for any preset.

### Blend

Blend is the third **Capture mode**, beside Trichrome and Single Exposure. Pick it in the
calibration window, or by hand with **Set by Hand**. Blend calibrates as Trichrome does, so a
selected Trichrome or Blend preset can switch between the two. A Single Exposure preset cannot,
because its levels are for three LEDs lit together. Set the camera to RAW.

A Blend calibration, with **Create Sensor Crosstalk Profile** on (the default), also saves a
sensor crosstalk profile under the preset's name. It reads every sensor channel in the last
shot of each LED, near the exposure target. Each Blend frame's roll takes that profile, with
Linear RAW.

A Blend preset with no profile of its own (one set by hand, or a Trichrome preset switched to
Blend) shows **Sensor profile** instead, and Scan waits until you pick one. The vials button
opens **Calibrate Sensor**, where **Capture from Camera…** makes a profile from the bare light.

**Shutter release** sets what holds the shutter open:

- **USB.** NegPy sets the shutter to Bulb and presses the camera's bulb control over the
USB cable: `eosremoterelease` on Canon, the `bulb` toggle on Nikon and Sony. A body
that has neither cannot use USB.
- **Cable.** A release cable goes from the Scanlight's 3.5mm jack to the camera. The light
holds the shutter line for the whole exposure, which the firmware limits to 2.55 s. NegPy
sets Bulb over USB if the body lists it. Otherwise, set Bulb on the camera. The camera
stays on USB, because the file still comes in over USB.

Each channel stays lit for the preset's shutter time at the preset's level. NegPy times
the channels, so a channel shorter than 0.2 s is dimmed and lit longer, with the same
light. All three levels change by one factor, so the channels keep their balance. A level
never goes below 40, the calibration's floor. With Cable, a long exposure is brightened to
fit the pulse; if it still does not fit, the scan stops and asks you to open the aperture
or raise the ISO and calibrate again. The LEDs are not fully linear, so a dimmed or
brightened Blend can expose a little differently from the Trichrome scan of the same
preset. After the exposure, the body goes back to its shutter step. A whole-strip scan keeps the
camera in Bulb from frame to frame, and puts the shutter back before the Flat Field shots. A built-in
**Trichrome** preset uses the camera's shutter step as the time, so meter first.

The light stays dark for 0.35 s after the Bulb press, because a camera opens its shutter
some time after the press. A body that opens later loses the start of the red light. To
measure your body's lag, close NegPy, leave a frame in the light and run
`uv run python scripts/bulb_lead_probe.py`. It prints the shortest safe lead.

**Scan.** Pick an output folder and a preset, then press **Scan** for each frame. Files go
to an auto-numbered per-roll subfolder and are imported and merged automatically. A Single Exposure preset writes one file per frame. **Retake** shoots the current frame again
to an auto-numbered per-roll subfolder and are imported and merged automatically. A Single Exposure or Blend scan writes one file per frame. **Retake** shoots the current frame again
without advancing the counter. **Capture Delay** holds both for up to 10 s before the first
exposure, so a table that shakes when you let go of the camera can settle; **Stop** ends the
wait. **Delay between exposures** adds a pause between the red, green and blue captures, for
Expand Down Expand Up @@ -181,8 +230,8 @@ described above.

- Close Dock Connect before you open the Scan tab. Only one program can use the dock's port.
- NegPy uses a Scanlight first. To scan with the dock, disconnect the Scanlight.
- The dock has no white or infrared light. A white-light preset lights red, green and blue
together at the same level.
- The dock has no white LED and no infrared light. NegPy still offers the white-light presets
and lights red, green and blue together at the same level for them.
- **Film** sets what the dock does when you click Scan:
- **Off**: the film stays where it is.
- **Next Frame**: after each scan, the dock moves the film to the next frame.
Expand All @@ -199,14 +248,27 @@ described above.
stays in the `flatfield/references` folder, outside the roll. A strip that stops before its
end gets no flat. The flat never comes first: after the dock moves the film out of the
window and back, it does not advance.
- Insert the strip after the light status shows the dock and before you open Live View. The
dock then finds the first frame itself. Opening the dock's port resets the dock, so it does
not know the position of a strip that was in it before.
- Put the strip in the dock's entrance. Firmware 1.0.238 and later align it to the first frame
on their own. With **Next Frame** or **Whole Strip**, Scan feeds a strip that still waits at
the entrance (`INSERT`), such as one that was in the dock when NegPy opened its port, because
opening the port resets the dock.
- While NegPy uses the dock, the dock's S button works as Scan: it starts a scan, or stops one.
The dock answers the press with its own exposure first, so the scan starts a few seconds
later. An arrow button nudges the film on a tap and moves it while you hold it. NegPy
S acts only while a frame is in the window: with no strip aligned, the dock looks for the next
frame instead. The scan starts at the press. The dock also makes a short exposure of its own,
and the film moves only after it ends. An arrow button nudges the film on a tap and moves it while you hold it. NegPy
leaves the dock's button mode (`BTNMODE`) as you set it.
- When NegPy releases the dock, it puts back the dock's saved light mode and control mode.
- While NegPy uses the dock, a tap of the dock's S button works as Scan: it starts a scan, or
stops one. NegPy reads the button (`BTN?`) and acts when you let go. The dock also acts on S
itself: with a frame in the window it makes a short exposure of its own, and the film moves
only after that ends. With a strip at the entrance, a tap lines the strip up first. A hold of
S is left to the dock, which ejects the strip on firmware 1.0.238.
- While NegPy uses the dock, an arrow button nudges the film on a tap and moves it while you
hold it (`BTNMODE JOG`), because Next Frame and Whole Strip move whole frames. NegPy puts the
dock's own button mode back when it releases the dock.
- While NegPy uses the dock, the dock's auto-sleep is off (`SLEEP 0`), because a sleeping dock
answers nothing. If the dock goes silent anyway, NegPy opens its port again on the next use.
- When NegPy releases the dock, it puts back the dock's saved light mode, control mode, button
mode and sleep timeout.

The dock's own pulsed light stays with Dock Connect.

Expand Down Expand Up @@ -249,19 +311,20 @@ assigns it on every scan. You assign a Calibration-panel profile yourself, once
| Tethered camera and Scanlight, manual preset | [C](#c-a-manual-preset) | Calibration panel | You |
| Files shot without NegPy | [D](#d-files-not-scanned-with-negpy) | Calibration panel | You |

All four apply to Single Exposure frames only. A Trichrome preset needs no profile.
All four apply to Single Exposure and Blend frames only. A Trichrome scan needs no profile. A
calibrated Blend preset makes its own profile, as in workflow A.

#### A. The preset makes the profile

The default, for calibrated Single Exposure presets.
The default, for calibrated Single Exposure and Blend presets.

Once per film stock:

1. Load the film. In the **Preset** panel, press **+** beside the preset
dropdown and pick **Calibrate on the Film Base…**. The calibration window opens with its own live view.
2. Set the ISO and the aperture you scan with, click the clear film base and name the
preset after the stock.
3. Pick **Single Exposure**, leave **Create Sensor Crosstalk Profile** on and press **Calibrate & Save**.
3. Pick **Single Exposure** or **Blend**, leave **Create Sensor Crosstalk Profile** on and press **Calibrate & Save**.

For every roll of that stock:

Expand Down Expand Up @@ -358,6 +421,8 @@ LED at a time). Its remaining color error is film-dye crosstalk, which the densi
| Live view is black | The 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 preview | libgphoto2's entry for this body has no preview capability (Sony a6000), or it is connected in MTP mode, where no body has it. | If the message names MTP, set the USB mode to **PC Remote** and reconnect. If not, scanning works, but you set framing and focus on the camera, and calibration is not available. |
| Capture says the camera returned JPEG instead of RAW | The image-quality setting is JPEG, or RAW+JPEG selected the processed file. | Set image quality to **RAW only**, then retry. |
| A Blend scan says the camera did not go to Bulb | The drive mode is continuous (a Sony body refuses Bulb in any burst mode), or the mode dial is not on M. | Set the drive mode to single shooting and the mode dial to M, then scan again. |
| A Blend scan says the camera sent no RAW file | The body is not in Bulb, it is set to JPEG, or the release cable does not reach it. | Set Bulb and RAW only. With **Cable**, check that the cable is in the Scanlight's 3.5mm jack and that the shutter fires from the light. |
| The aperture stepper is grayed out | The lens has no electronic diaphragm. | Set the aperture on the lens. |
| A setting snaps back to its old value | Property writes are asynchronous. | NegPy polls until the value lands and logs a warning if it never does. Then the setting is not writable in the body's current mode. Try **M**. |
| The Scanlight is not detected | Wrong USB-C port. | Only one of the two ports carries data; the other is power only. Use the data port. |
Expand All @@ -367,6 +432,8 @@ LED at a time). Its remaining color error is film-dye crosstalk, which the densi
## Notes and limitations

- **USB only.** libgphoto2 can reach some cameras over the network, but not Sony bodies.
- **Blend is untested on a real body.** The USB bulb controls follow libgphoto2's names for
Canon, Nikon and Sony, and the simulated rig covers both releases.
- **Only Sony bodies are tested.** NegPy looks up every control and assumes no vendor.
`iso` and `shutterspeed` have the same names everywhere. The aperture is `f-number` on
Sony and Panasonic, and `aperture` on Canon, Nikon, Fujifilm, Olympus and Sigma. The RAW
Expand Down
Loading
Loading