nkscan
A cross-platform and performant library and command line application for Nikon film scanners
Support
I only own a Coolscan 9000, but through reverse-engineering of the Nikon binaries and the work of others, we are slowly adding support for more scanners. Please reach out if you have a scanner we can test! Even just dumps of USB/SCSI payloads of a normal scan would be incredibly useful.
Our goal is to support all the scanners supported by Nikon Scan, which are enumerated here
- ✅ Supported, and run against the hardware
- ⚠️ Untested but theoretically should work
- ❌ Not started
Every scanner Nikon Scan supported is recognized by name, including the ones with no driver:
nkscan --list will report an attached 8000, 4000 or IV and say it has no driver rather than
staying silent about it.
Medium Format Scanners
| Scanner \ Holder | 835M | 835S | 869S | 869G | 869GR | 869M | 816 | 8G1 |
|---|---|---|---|---|---|---|---|---|
| 9000 | ❌ | ❌ | ✅ | ⚠️ | ⚠️ | ❌ | ❌ | ❌ |
| 8000 | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
35mm Scanners
| Scanner \ Holder | SA-21 | IA-20/21 | MA-20/21 | SA-30 | SF-210/200 |
|---|---|---|---|---|---|
| 5000 | ✅ | ❌ | ⚠️ | ⚠️ | ❌ |
| 4000 | ❌ | ❌ | ❌ | ❌ | ❌ |
| V | ✅ | ❌ | ⚠️ | ✅ | ❌ |
| IV | ❌ | ❌ | ❌ | ❌ | ❌ |
MacOS Firewire support is planned, but just stubbed out for the moment. It should work for USB scanners, like the Coolscan V.
Caveat Emptor
While the 9000 has the best support right now, there are still some gaps in capability based on what I have.
- The Coolscan 9000 workflow assumes a medium format strip film holder. The frame detection algorithm will not work for others, but you can manually place the frames for the time being. If someone has the 35 holder or others, please reach out and we can add the missing logic.
- The Coolscan V workflow assumes a strip holder, not a full roll holder. I need more dumps of payloads/RE work to understand the typical flow for those (or the other inserts like the bulk slide loader)
- The Coolscan 5000 has now scanned with an SA-21, on Windows. The roll transport, the multi-sample readout and the metering path are all still inference, so treat anything past a strip scan as unproven.
- Which film holder a medium format body has loaded cannot yet be identified exactly. The
scanner reports a holder class rather than a part number, and a class does not say whether
it is an FH-869S, an FH-869G or a 35 mm carrier, so capabilities fall back to what every
holder in the family can do. See
docs/HOLDERS.md.
If you would like to contribute support for new scanners, please take a look at scsi_proxy/README.md
Usage
Usage: nkscan [OPTIONS]
Options:
--device <DEVICE> Which scanner to use, as `--list` reports it. Only needed when more than one is attached
--list List the scanners attached and exit, without touching any of them
--frames <FRAMES> Frames on the loaded strip
--frame <FRAME> Which frames to actually scan, zero-indexed and comma separated. All of them by default
--dpi <DPI> Resolution in DPI. One of the firmware's divisions of the sensor's native pitch
--gain <R,G,B[,IR]> Fixed per-channel analog gain as `red,green,blue[,ir]`, which turns autoexposure off
--focus <FOCUS> Focus: `auto` to let the scanner find it on each frame, or a fixed setpoint [default: auto]
--ir Capture the infrared plane for dust removal
--basename <BASENAME> Where to write, as a path prefix. Each frame becomes <basename>_<n>.tiff, and its infrared mask <basename>_<n>_ir.tiff [default: scan]
--offset <OFFSET> Where each frame starts along the feed, in mm, comma separated, last value repeating
--pitch <PITCH> Frame pitch in mm, overriding what the scanner would use
--lock-wb Hold the white balance during autoexposure, so the film keeps its cast. Not on the LS-50
--multisample <MULTISAMPLE> Multisampling, trading scan time for noise. One of 1,2,4,8,16. LS-9000 only [default: 1]
--singleline Single-line CCD mode. Slow, but may improve banding. LS-9000 only
--eject Send the film back out when everything is done
--batch Scan every strip of the roll, ejecting and pausing to reload in between
-h, --help Print help (see more with '--help')
-V, --version Print version
Example Workflow
Raw Negatives for External Inversion
Say I have a LS-9000 and I invert with either NegPy or Negative Lab Pro. For this to work well, I want essentially the raw scans out of the scanner, but with equal exposure across all frames so I can perform "roll analysis".
So, I load up the film holder with the first strip (in my case 6x6 negatives, with three frames), and scan in "batch mode". For this, I tell the program how many frames per strip, include the IR pass, lock the whitebalance during autoexposure of the first frame (which will be applied to every farme), and enable 2x multisampling.
nkscan --frames 3 --ir --lock-wb --multisample 2 --batch
Personally, I find this much faster than anything I could do in NikonScan or Vuescan.
Python
We also ship the driver as a Python extension, which simplifies use in from Python-based scanning/inversion programs. The image data comes back as zero-copy numpy arrays in linear 16-bit ADC counts.
pip install nkscan
import nkscan
device = nkscan.list_devices()[0]
print(device.id, device.model, device.capabilities.dpi)
with nkscan.Session(device.id) as session:
# Three 6x6 frames at a 56 mm pitch, holding one exposure across all of them
session.prepare(frames=3, pitch_mm=56.0, gain=(283048, 202864, 166589))
for frame in range(3):
result = session.scan(frame, dpi=2000, ir=True,
progress=lambda read, total: print(f"\r{read}/{total}", end=""))
result.rgb # (height, width, 3) uint16
result.ir # (height, width) uint16, or None
session.eject()
session.overview() takes the low-resolution pass Nikon Scan calls a thumbnail, returning the
whole strip in one image alongside the resolution it ran at, which is what maps a point on the
thumbnail back to a position on the film.
session.capabilities answers for the model and the loaded adapter, since most of what a
scanner will do depends on what is in it: ejecting returns a holder on one adapter and rewinds a
cartridge on another. Anything the scanner will not do raises nkscan.UnsupportedError, which
carries feature and reason so a caller can tell "this scanner cannot" (not_present) from
"this library does not yet" (not_implemented) without reading the message.
The scan may take minutes, so a progress callback can drive a UI and returning False from it stops the scan.
Failures worth retrying share a nkscan.TransientError base, so one except covers a link glitch or a short read without swallowing a real problem like nkscan.MediaError.
Building it from source needs maturin; the dev shell in flake.nix has it, along with a Python carrying numpy.
maturin develop
The type stub is generated rather than written, so it cannot fall out of step with the bindings:
cargo run --features python --bin stub_gen
License
Dual licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE)
- MIT license (LICENSE-MIT)
at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Related Projects and References
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nkscan-0.2.0.tar.gz.
File metadata
- Download URL: nkscan-0.2.0.tar.gz
- Upload date:
- Size: 253.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c545fcc2b81d19f8db192d9d17befc394cf6c0cf8ac1550e6b072896faf8037
|
|
| MD5 |
4248b852dfd8470a1c8fcdf2a84f1b91
|
|
| BLAKE2b-256 |
05d3615036d9fa3bd799bceeed7220c683608a0969207bb9ec73b4000807ebd6
|
Provenance
The following attestation bundles were made for nkscan-0.2.0.tar.gz:
Publisher:
release.yml on activexray/nkscan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nkscan-0.2.0.tar.gz -
Subject digest:
0c545fcc2b81d19f8db192d9d17befc394cf6c0cf8ac1550e6b072896faf8037 - Sigstore transparency entry: 2314284745
- Sigstore integration time:
-
Permalink:
activexray/nkscan@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/activexray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nkscan-0.2.0-cp313-abi3-win_amd64.whl.
File metadata
- Download URL: nkscan-0.2.0-cp313-abi3-win_amd64.whl
- Upload date:
- Size: 417.0 kB
- Tags: CPython 3.13+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
68e147308f47abfffb5009c5c49c0813aea2209fb5963282bf1ecd483369df29
|
|
| MD5 |
dbf193d7792c81e8a29b2a1ecac255ff
|
|
| BLAKE2b-256 |
b14f853a347095615381bc7c4c5bf902cab7173baac23c45fe4cbdd9885f5c90
|
Provenance
The following attestation bundles were made for nkscan-0.2.0-cp313-abi3-win_amd64.whl:
Publisher:
release.yml on activexray/nkscan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nkscan-0.2.0-cp313-abi3-win_amd64.whl -
Subject digest:
68e147308f47abfffb5009c5c49c0813aea2209fb5963282bf1ecd483369df29 - Sigstore transparency entry: 2314284943
- Sigstore integration time:
-
Permalink:
activexray/nkscan@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/activexray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nkscan-0.2.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: nkscan-0.2.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 679.5 kB
- Tags: CPython 3.13+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
05481a417bdb03e4f22e9c829043196e47ab9359fd33c5a44073630da01eddee
|
|
| MD5 |
3b4adbb8a604a8a33114eab3b4583441
|
|
| BLAKE2b-256 |
a432543747701c76fbe094f0f8a722fa7103b50713a1cf7c83f09a610e07f8fe
|
Provenance
The following attestation bundles were made for nkscan-0.2.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on activexray/nkscan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nkscan-0.2.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
05481a417bdb03e4f22e9c829043196e47ab9359fd33c5a44073630da01eddee - Sigstore transparency entry: 2314284812
- Sigstore integration time:
-
Permalink:
activexray/nkscan@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/activexray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nkscan-0.2.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: nkscan-0.2.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 634.0 kB
- Tags: CPython 3.13+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60bfd950b556198e95b3f688220f710eaa1419ce94e243cb206cbf6770d36e15
|
|
| MD5 |
94218b887a7a0694cd851a203a93105b
|
|
| BLAKE2b-256 |
72abae9dd681f1137dac262c60cc58ab6df9bd18d9731c4bf21b2c78c2b26d39
|
Provenance
The following attestation bundles were made for nkscan-0.2.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on activexray/nkscan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nkscan-0.2.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
60bfd950b556198e95b3f688220f710eaa1419ce94e243cb206cbf6770d36e15 - Sigstore transparency entry: 2314284863
- Sigstore integration time:
-
Permalink:
activexray/nkscan@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/activexray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nkscan-0.2.0-cp313-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: nkscan-0.2.0-cp313-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 554.7 kB
- Tags: CPython 3.13+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f47c0fe3d5c41f596a00a9d055c4e809a3b6ecc3871fdeec65b6cc0f1eaa828b
|
|
| MD5 |
b0967f10df56299acaa048e47a3f2759
|
|
| BLAKE2b-256 |
223b23fdae6bb5a4527e2131fcb5474b2e255ee74d37c31290bc3b939ecad5db
|
Provenance
The following attestation bundles were made for nkscan-0.2.0-cp313-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on activexray/nkscan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nkscan-0.2.0-cp313-abi3-macosx_11_0_arm64.whl -
Subject digest:
f47c0fe3d5c41f596a00a9d055c4e809a3b6ecc3871fdeec65b6cc0f1eaa828b - Sigstore transparency entry: 2314284989
- Sigstore integration time:
-
Permalink:
activexray/nkscan@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/activexray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Trigger Event:
push
-
Statement type:
File details
Details for the file nkscan-0.2.0-cp313-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: nkscan-0.2.0-cp313-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 559.8 kB
- Tags: CPython 3.13+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0240c6dd08cb62c9352e5129c98dc017feba9dfacf558866d48058d376e025af
|
|
| MD5 |
a78bf8226ecb448bca362d1895fe5de4
|
|
| BLAKE2b-256 |
de9f607e4b6236a4ab850661d654d2058aa5b236231b6ef6091f3486f81c9382
|
Provenance
The following attestation bundles were made for nkscan-0.2.0-cp313-abi3-macosx_10_12_x86_64.whl:
Publisher:
release.yml on activexray/nkscan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nkscan-0.2.0-cp313-abi3-macosx_10_12_x86_64.whl -
Subject digest:
0240c6dd08cb62c9352e5129c98dc017feba9dfacf558866d48058d376e025af - Sigstore transparency entry: 2314284899
- Sigstore integration time:
-
Permalink:
activexray/nkscan@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/activexray
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66200fa2a5f7a60d124fd2bb13b0c90c28a78538 -
Trigger Event:
push
-
Statement type: