- Python 63.1%
- TypeScript 30.7%
- CSS 5.6%
- Dockerfile 0.3%
- Shell 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
L'agent lit les profils de films NegaFix de SilverFast depuis un dossier fourni par le titulaire d'une licence (NEGSCAN_NEGAFIX_DIR, par défaut <état>/negafix) ; le dépôt n'en contient aucun. - negafix.py : format décodé (en-tête de 256 octets, XOR par la suite 0x7B − 0x39·i, blocs de courbes à n points × 3 canaux en 0..32767), catalogue par jeu et par fabricant, inversion par profil ; - interprétation retenue après essais sur de vrais négatifs Kodak et Fuji : entrée = négatif inversé et normalisé par canal, encodé γ 2,2 ; sortie = lumière linéaire. Les trois blocs, au rôle non documenté, sont proposés comme trois variantes ; - recette : emulsion (« <jeu>/<film> ») et emulsion_variant, repris par « Appliquer à toutes », le copier-coller et les nouvelles vues ; - GET /api/emulsions ; onglet Tonalité : jeu (ProScan 7200 par défaut, le plus proche du 10T), film groupé par fabricant, variante. Tests sur des profils synthétiques. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .forgejo/workflows | ||
| agent | ||
| deploy | ||
| docs | ||
| scripts | ||
| ui | ||
| .dockerignore | ||
| .gitignore | ||
| compose.yaml | ||
| Dockerfile | ||
| LICENSE | ||
| README.md | ||
negative-scanner
Outil de numérisation de film (négatifs couleur et N&B, diapositives) pour scanners de film, à commencer par le Reflecta ProScan 10T. Il est simple par défaut et dispose d'un mode avancé. Ses fonctions s'inspirent de SilverFast.
Il se compose de deux parties :
- un agent en Python,
negscan, qui pilote le scanner via SANE et sert l'interface ; - une interface web en React, utilisable depuis la machine du scanner ou depuis une tablette ou un PC du réseau local.
Choix techniques, interface prévue et feuille de route : docs/ARCHITECTURE.md.
État
Ce qui fonctionne :
-
Capture : un aperçu rapide pour vérifier le cadrage, puis un scan brut pleine résolution (RGB + infrarouge, 16 bits linéaire), archivé tel quel dans un rouleau. La touche Espace scanne la vue placée. Le temps restant s'affiche (d'après les durées mesurées) et dans le titre de l'onglet ; un signal sonore discret annonce la fin du scan, le moment d'avancer la bande. L'étape Capture indique l'espace libre en nombre de vues ; un scan qui ne tiendrait pas sur le disque est refusé avant de lancer le scanner. Le scanner est détecté dès qu'on le branche, sous son nom (ProScan 10T). En mode avancé, le multi-échantillonnage moyenne 2 ou 4 passes recalées : il réduit le bruit du capteur, plus fort que le grain dans les zones denses d'un négatif.
-
Positif automatique : inversion du négatif (masque orange retiré, couleurs équilibrées) selon le type de film du rouleau. On peut basculer à tout moment entre Positif et Négatif. L'agent reconnaît un négatif couleur ou un N&B argentique et propose le bon réglage.
-
Développement :
- recadrage à la souris (libre, 3:2, 4:3 ou 1:1), avec un recadrage automatique qui retire le passe-vues ;
- rotation, miroir ;
- exposition, contraste, température, teinte, saturation ;
- points blanc et noir à la pipette ;
- netteté (masque flou) et réduction du grain, avec une loupe 1:1 qui montre les pixels exportés ;
- dépoussiérage infrarouge, avec l'affichage des défauts détectés (indisponible sur N&B argentique) ;
- en mode avancé : histogramme, affichage de l'écrêtage, courbes RVB et par canal, rayon et seuil de netteté ;
- zoom sur l'image : molette ou pincement sous le curseur, double-clic pour 100 % de l'aperçu, touches + − 0 ; on déplace l'image zoomée au bouton du milieu, avec Espace + glisser ou en saisissant le fond de la scène ;
- comparaison avant/après (inversion automatique seule) avec un séparateur ;
- densitomètre en mode avancé : valeur développée, densité du film et infrarouge sous le curseur ;
- raviver les couleurs d'une diapositive passée ;
- profils d'émulsion NegaFix (onglet Tonalité) : avec une licence SilverFast, les courbes mesurées de chaque film remplacent l'inversion adaptative, en trois variantes (voir « Profils NegaFix » plus bas) ;
- en mode avancé : couleurs sélectives (teinte, saturation, luminosité de six teintes, les gris ne bougent pas) ;
- annuler/rétablir (Ctrl+Z, Ctrl+Maj+Z), copier/coller les réglages d'une vue à l'autre (Ctrl+C, Ctrl+V), « Appliquer à toutes les vues » ;
- suppression d'une vue (déplacée dans la corbeille du rouleau, d'où on peut la restaurer).
Tout est enregistré dans une recette par vue : le brut ne change jamais. Une vue qui vient d'être scannée reprend l'orientation et les réglages de la précédente du rouleau.
-
Rouleaux : nom, date des photos (même approximative), suppression (vers la corbeille de la bibliothèque).
-
Export et téléchargement depuis le navigateur, au choix :
- TIFF 16 bits ;
- JPEG pleine résolution ;
- JPEG 3 000 px ;
- scan brut.
Une planche contact réunit toutes les vues du rouleau sur un JPEG, numérotées, avec le nom, la date et le type de film.
Les fichiers portent une légende (rouleau, vue, type de film) et leurs dates : prise de vue si elle est connue, numérisation. En mode avancé, un motif règle leur nom (
{date}_{nom}_{vue}…). -
Accès réseau en TLS, avec appairage des appareils.
Ce qui n'existe pas encore : profils d'émulsion, exports en Adobe RGB ou ProPhoto (sans calibration du capteur, ils ne contiendraient pas plus de couleurs que le sRGB). L'exposition du capteur par canal a été mesurée et abandonnée : sur ce scanner, elle n'améliore pas le rapport signal sur bruit (voir l'architecture).
Prérequis
-
Linux, avec
sane-utils(fournitscanimage) et le backendpieusb, présent danslibsane1sur Debian. -
Python ≥ 3.11 et Node.js ≥ 20.
-
Un accès au scanner pour l'utilisateur. Sur Debian, c'est déjà le cas grâce aux règles udev de
libsane1(groupescanner). Sinon :sudo tee /etc/udev/rules.d/60-negscan.rules >/dev/null <<'EOF' SUBSYSTEM=="usb", ATTR{idVendor}=="05e3", ATTR{idProduct}=="0145", TAG+="uaccess" EOF sudo udevadm control --reload-rules && sudo udevadm trigger
Pour vérifier : scanimage -L doit afficher device 'pieusb:libusb:…' is a PIE SF Scanner film scanner.
Branchez le scanner directement sur la machine, sans hub ni rallonge : le fabricant les déconseille.
Installation
cd agent
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
cd ../ui
npm install
npm run build
Sur Debian, si python3 -m venv échoue parce que ensurepip est absent, deux solutions :
- installer
python3-venv; - ou créer le venv avec
--without-pip, puis y installer pip depuishttps://bootstrap.pypa.io/pip/pip.pyz(.venv/bin/python pip.pyz install pip).
Utilisation
agent/.venv/bin/negscan serve # ouvre le navigateur sur http://127.0.0.1:8765
agent/.venv/bin/negscan serve --mock # scanner simulé, pour travailler sans le matériel
L'URL affichée au démarrage contient un jeton (#token=…). Elle appaire automatiquement le navigateur de ce poste.
Scanner, exporter, télécharger
- Support : choisissez ou créez un rouleau, puis indiquez le type de film (négatif couleur, négatif N&B, diapositive).
- Capture : placez la vue, faites un aperçu rapide, puis Scanner la vue n.
- Archive : 4 000 dpi, environ 1 min 40 et 176 Mo par vue.
- Rapide : 2 000 dpi, environ 1 min 10 : le gain de temps est faible, le scanner calibre et déplace sa tête au même rythme.
- En mode avancé : toute résolution, et les modes RGB ou niveaux de gris.
- Développement : recadrez, redressez, réglez. L'aperçu suit chaque réglage, et un double-clic sur un curseur le remet à sa valeur par défaut. Netteté et grain ne se voient qu'à 100 % : la Loupe 1:1 s'ouvre d'elle-même, un clic sur l'image la déplace. Avant / après compare avec l'inversion automatique seule.
- Export : choisissez le format, puis Télécharger. Les exports appliquent la recette de chaque vue. Pour tout le rouleau : Tout télécharger (ZIP) après l'export, ou Tous les bruts (ZIP). Le navigateur reçoit le fichier, même depuis une tablette. Exporter les n vues prépare tout le rouleau et affiche un lien par fichier.
Les fichiers restent aussi sur la machine du scanner :
~/Scans/<rouleau>/
roll.json nom, type de film, date des photos
raw/0001.tif scan brut : TIFF 16 bits linéaire, RGB + infrarouge (4ᵉ canal), non développé
raw/0001.json métadonnées d'acquisition
cache/0001.npy aperçu réduit (vignettes, mesures d'inversion)
edits/0001.json recette : orientation, recadrage, réglages
corbeille/… vues supprimées (restaurables depuis l'étape Support)
export/… TIFF et JPEG exportés
~/Scans/corbeille/ rouleaux supprimés
Prévoyez de la place : un rouleau de 36 vues en Archive occupe environ 6 Go de bruts. Pour changer de dossier : --library, NEGSCAN_LIBRARY_DIR ou library_dir dans la configuration.
Depuis un autre appareil du réseau local
agent/.venv/bin/negscan serve --listen 0.0.0.0:8765
- Au premier lancement, l'agent génère un certificat auto-signé. Il affiche son empreinte SHA-256 : comparez-la à celle que montre le navigateur avant d'accepter l'avertissement. Pour utiliser votre propre certificat :
--tls-cert/--tls-key. - Ouvrez
https://<machine>.local:8765sur l'autre appareil. - Saisissez le code d'appairage affiché au démarrage. Il vaut 10 minutes et ne sert qu'une fois. Pour en obtenir un autre :
- lancer
negscan pair; - ou passer par Appareils → Appairer un nouvel appareil depuis un appareil déjà appairé.
- lancer
- Pour lister et révoquer les appareils :
negscan devices,negscan devices revoke <id>, ou le dialogue Appareils.
Hors loopback, l'agent ne sert jamais en clair.
Pour une machine dédiée (Raspberry Pi, petit serveur), voir le service systemd deploy/negscan.service.
Configuration
~/.config/negscan/config.toml, facultatif :
listen = "0.0.0.0:8765"
public_hosts = ["scanner.maison.lan"] # noms supplémentaires acceptés dans l'en-tête Host
library_dir = "~/Scans" # rouleaux, scans bruts et exports
# tls_cert = "/chemin/cert.pem"
# tls_key = "/chemin/key.pem"
L'état (appareils appairés, codes, certificat) est stocké dans ~/.local/share/negscan/. On peut le déplacer avec --state-dir ou NEGSCAN_STATE_DIR.
Docker
Une image regroupe l'agent, l'interface compilée et SANE (scanimage, backend pieusb) :
docker compose pull && docker compose up -d # image publiée : src.zerolatitude.dev/gquillery/negscan
docker compose up -d --build # ou construite depuis les sources
docker compose logs negscan # adresse de l'agent, empreinte du certificat
Puis ouvrir https://<machine>.local:8765 depuis n'importe quel appareil du réseau.
L'image est construite et publiée par le CI à chaque push sur main (:latest et :sha-<commit>).
- Accès libre. Le conteneur est pensé pour une machine sans écran : il écoute sur tout le réseau local (
0.0.0.0:8765, en TLS avec un certificat auto-signé) et ne demande ni jeton ni appairage (NEGSCAN_NO_AUTH=1). Toute personne du réseau local peut donc piloter le scanner, et voir et télécharger les scans. Les en-têtesHostetOriginrestent vérifiés : une page d'un autre site ne peut pas piloter l'agent depuis le navigateur d'un visiteur. Pour revenir à l'appairage des appareils, mettreNEGSCAN_NO_AUTH=0dans un fichier.env; pour n'écouter que sur la machine,NEGSCAN_LISTEN=127.0.0.1:8765. - Réseau de l'hôte. Le conteneur partage le réseau de la machine (
network_mode: host). L'agent y voit le vrai nom et les vraies IP, que son certificat TLS et son contrôle de l'en-têteHostattendent. On le joint par le nom de la machine,<nom>.localou n'importe laquelle de ses IP ; un autre nom (DNS local, reverse proxy) se déclare dansNEGSCAN_PUBLIC_HOSTS, sans quoi l'agent répond « hôte non autorisé ». - Scanner. Tout
/dev/bus/usbest monté, avec une règle cgroup pour les périphériques USB : le scanner reste accessible après un débranchement, même s'il change de numéro. Ce sont les droits posés par udev sur l'hôte qui décident :- sur un poste de bureau, udev donne le scanner à l'utilisateur connecté (ACL
uaccess) : le conteneur tourne sous l'uid 1000, à ajuster avecNEGSCAN_UID; - sur une machine sans session (Raspberry Pi, serveur), donner le scanner au groupe
scannerpar une règle udev (MODE="0660", GROUP="scanner", sur le modèle de celle des prérequis) et indiquer son gid dansSCANNER_GID(getent group scanner, 102 par défaut).
- sur un poste de bureau, udev donne le scanner à l'utilisateur connecté (ACL
- Données. Les appareils appairés, le certificat et les rouleaux vivent dans le volume
negscan-data(/data/state,/data/scans). Pour garder les scans dans un dossier de l'hôte, monter ce dossier sur/data/scans; il doit appartenir à l'uid du conteneur. - Appairer un appareil (avec
NEGSCAN_NO_AUTH=0) :docker compose exec negscan negscan pair. - Le conteneur tourne sans privilèges : système de fichiers en lecture seule (sauf
/dataet/tmp), aucune capacité,no-new-privileges.
Profils NegaFix
negscan sait lire les profils de films NegaFix de SilverFast, mais n'en contient aucun : ce sont des données de LaserSoft Imaging, réservées aux titulaires d'une licence. Copiez les dossiers de profils (un sous-dossier par jeu, ProScan7200/, Other/…, contenant les fichiers .bin) dans le dossier negafix de l'agent :
- sans Docker :
~/.local/share/negscan/negafix/(ou le dossier désigné parNEGSCAN_NEGAFIX_DIR) ; - avec Docker :
/data/state/negafix/dans le volume, par exempledocker compose cp ProScan7200 negscan:/data/state/negafix/.
Ils apparaissent dans l'onglet Tonalité, après un redémarrage de l'agent. Le ProScan 10T n'a pas de jeu à lui ; le jeu ProScan 7200 (même électronique Pacific Image) est proposé par défaut. Le format et l'interprétation retenue sont décrits dans negafix.py.
Développement
scripts/dev.sh # agent + Vite avec rechargement à chaud (ouvrir l'URL « Sur ce poste »)
scripts/dev.sh --mock # idem avec le scanner simulé
cd agent && .venv/bin/pytest
cd ui && npm run typecheck
La CI Forgejo (.forgejo/workflows/ci.yml) fait tourner les tests de l'agent sous Python 3.11, la version minimale (celle de Raspberry Pi OS bookworm), et vérifie les types et la compilation de l'interface, à chaque push sur main.
Dans un éditeur en Flatpak, l'USB n'est visible que depuis l'hôte. scripts/dev.sh y lance donc l'agent via flatpak-spawn --host.
Licence
GPL-3.0-or-later.