Contributors with read access cannot use GitHub's assignee UI, so comment on the issue:
/assign: assign the issue to yourself/unassign: remove yourself from the issue
A 👀 reaction on your comment confirms that the command ran.
NegPy requires Python 3.13+, uv and a Rust toolchain.
Install uv and rustup. rustup reads the toolchain version from negpy_core/rust-toolchain.toml.
The negpy_core/ folder is a Rust library with NegPy's CPU kernels. The first uv sync compiles it. After you edit a Rust file, the next uv run or make test compiles it again. negpy_core builds no C or C++ code: cargo-deny (negpy_core/deny.toml) rejects a dependency that does. Its tests compare against opencv, which only the dev group installs; make install (uv sync --all-groups) brings it.
Scanner support (optional):
-
SANE (Linux/macOS): Coolscans and other SANE film scanners. Install the system library, then the
sanegroup (uv sync --group saneorpip install negpy[sane]):- Linux:
sudo pacman -S sane # arch sudo apt install libsane-dev # debian/ubuntu
- macOS:
brew install sane-backends
- Linux:
-
Plustek USB (Windows/macOS/Linux): the pyopticfilm driver (1.3.8 or later):
uv sync --group plustekorpip install negpy[plustek]. On Windows, pyopticfilm installslibusb-package, and the scanner needs WinUSB bound with Zadig in place of the vendor/SilverFast driver (07b3:18258200i SE,07b3:18248100 V2,07b3:181f8300i SE,07b3:0c3b7600i v1). See docs/PLUSTEK_WINDOWS.md. -
Nikon Coolscan (Windows/macOS/Linux): nkscan 0.14 or newer, shipped as wheels:
uv sync --group nkscanorpip install negpy[nkscan]. On Linux, USB needs a udev rule for vendor04b0, and FireWire/SCSI needs thesgmodule.
make installmake runNegPy keeps its databases, caches, presets, logs and override.toml in one user
directory, Documents/NegPy by default. If you also use NegPy for real work, give your
development builds their own directory: set NEGPY_USER_DIR to an absolute path in an
optional, gitignored .env.local, which the Makefile reads:
# .env.local
NEGPY_USER_DIR = $(HOME)/negpy-devhomemake run then uses (and creates) that directory.
Write $(HOME), not ~ or $HOME: make leaves ~ alone and reads $HOME as $H
followed by OME. make stops when the value is not absolute. On macOS your home is under
/Users/you, not /home/you/....
To reset it:
make clear-devhomeThis deletes the whole directory after a confirmation (FORCE=1 skips it). It refuses to
run if NEGPY_USER_DIR is unset or points at the default directory. Keep a copy of any
override.toml you rely on.
Two things stay outside the user directory:
.negpysidecars next to the source images. The next install that opens an image promotes its sidecar into its database. Sidecar export is off by default; if you turn it on, test against copies.- Exports.
make run-simStarts the app with a simulated camera, Scanlight and film scanners
(NEGPY_SIMULATE_HARDWARE=1). Only the gphoto2 module, the serial port and one scanner
backend are fake.
- Camera: exposes frames from a synthetic negative under the Scanlight's color and the body's shutter and ISO, saved as Bayer DNGs. Put the calibration ROI on the clear base band around the picture.
- Scanlight: a v4b that answers the firmware query and reports LED temperature.
- Film Scanner: the "Simulated" backend has a feeder (frames by index, IR, Eject), a prescan device (full-window preview, then crop) and a roll device (frames found on the strip).
negpy/domain/: core data models, types and interfaces.negpy/features/: image processing logic (Exposure, Geometry, Lab, etc.).negpy/infrastructure/: low-level system code (GPU resources, file loaders).negpy/kernel/: core system services (Logging, Config, caching).negpy/services/: high-level orchestration (Rendering engine, Export service).negpy/desktop/: PyQt6 UI (View, Controller, Workers).negpy_core/: Rust library for CPU kernels (src/<area>.rs), with its PyO3 wrappers insrc/py/.tests/: unit and integration tests.
Always run make format before committing.
- Ruff lints and formats.
- Type hints are required on every new function definition (
tyis enforced). Do not usecastto get around it. - Clear, short docstrings on classes and public methods.
- Double quotes for strings, snake_case for variables and functions, PascalCase for classes.
New features include unit tests (pytest) in tests/.
make testmake test skips tests marked slow (see addopts in pyproject.toml), including the performance metrics suite in tests/metrics/. To run it and write a JSON results file:
NEGPY_METRICS_OUT=metrics.json uv run pytest tests/metrics/ -m "slow" -qThe fixtures (Canon CR2, Nikon NEF, Sony ARW, Fuji RAF, Leica DNG) download from rawsamples.ch on the first run (~20–30 MB each) and are cached in ~/.cache/negpy-metrics/. A test skips if its download fails. To use a local file, set the per-format env var:
NEGPY_PERF_RAW_CR2=/path/to/file.CR2 \
NEGPY_METRICS_OUT=metrics.json \
uv run pytest tests/metrics/ -m "slow" -qAvailable overrides: NEGPY_PERF_RAW_CR2, NEGPY_PERF_RAW_NEF, NEGPY_PERF_RAW_ARW, NEGPY_PERF_RAW_RAF, NEGPY_PERF_RAW_DNG.
Every developer command runs through uv run:
make install: set up and sync the environment.make lint: Ruff checks.make type:tytype checks.make test: unit tests.make format: auto-format withcargo fmtand Ruff.make rust:cargo fmt --check,cargo clippy(warnings fail) andcargo testfornegpy_core.make all: format, Rust checks, lint, type and test in sequence.make clean: remove cache and build artifacts.make run-sim: run with simulated scanning hardware (see above).make clear-devhome: delete the development user directory (see above).
Wrap display text in tr("English source", name=value) and translate the whole template before inserting values. Use mark for catalog labels that also serve as lookup keys, then call tr when displaying them. Keep enum values, settings keys, paths and user text unchanged. Language is an application preference and takes effect after a restart.
Register languages in LANGUAGES (negpy/kernel/system/i18n.py), using the matching Qt catalog locale, and set their count forms in kernel/system/text.py. make translations updates every registered JSON catalog under negpy/translations/. Fill new entries, then run make translations-check to check coverage and placeholders for all catalogs. Localized panel guides use the same <!-- panel:key --> markers as docs/USER_GUIDE.md, plus src:<hash> of the English section they translate; tests/test_i18n.py names each stale panel and its new marker. Missing panels fall back to English. Include new catalog and guide files in the packaged resources.
For translation updates after releases or upstream merges, use the update-translations skill.
To build the standalone application for your current OS with PyInstaller:
make buildOn macOS, NEGPY_MACOS_ARCH sets the DMG's target architecture. For an Intel build:
NEGPY_MACOS_ARCH=x86_64 make build