lardon3d/docs/concepts/scan_sets.md
fy59 b84f860d86 refactor(core): freeze global maintenance baseline
Complete the A-to-Z Lardon3D maintenance and coherence pass.

Generalize host resource policy, remove the global CPU12 ceiling, preserve
host CPU/RAM reserves, scale Task capabilities through the Resource Governor,
and validate deterministic parallel GV execution.

Migrate Project DB to v23 with data-driven camera, lens, optical configuration
and calibration profiles, including manual lenses without EXIF.

Integrate safe optional LARDON SSD swap/scratch control with Governor and F10
drain/safe-to-unplug semantics.

Refactor the ncurses TUI into a runtime observatory with durable progress,
elapsed time, smoothed ETA, throughput, resource telemetry, Governor state,
optics workflow, colors and compact/no-color fallbacks.

Reconcile Queue lifetime, persistence, concurrency, comments, tests,
README, AGENTS and canonical documentation.

GLOBAL_MAINTENANCE_AUDIT=PASS/FROZEN
2026-09-01 08:00:47 +02:00

144 lines
7.1 KiB
Markdown

# ScanSets et catalogue d'images
## Statut
**IMPLEMENTED — ScanSet v1 et Image Catalog persistant v1.**
Un projet contient zéro ou plusieurs acquisitions logiques appelées
`ScanSet`. Un ScanSet vide est valide : il peut être créé avant la capture ou
l'import.
## Modèle durable
```text
Project
└── ScanSet (acquisition logique)
└── Capture (observation physique potentielle)
├── Image Assets source/dérivés (contenus physiques gérés)
├── Images (représentations logiques)
└── zéro ou une Image sélectionnée
```
- `scanset_id` identifie durablement une acquisition, indépendamment de son
nom.
- `image_id` identifie durablement une observation dans un ScanSet,
indépendamment du nom du fichier et de la tâche d'import.
- `asset_id` identifie un contenu physique. Son SHA-256, sa taille et son
chemin relatif décrivent le fichier géré par Lardon3D.
- La provenance d'une image conserve son nom original, son chemin source, son
instant d'import et, s'il existe, le `task_id` importeur.
- Un `Capture` est une association de catalogue dans un unique ScanSet ; il ne
remplace pas `image_id`. Il peut conserver par exemple les assets RAW et JPEG
d'un même déclenchement, sans inférer ce lien à partir de leurs noms.
- Seule une image logique explicitement sélectionnée est la représentation
courante proposée au pipeline scientifique ultérieur. La sélection ne modifie
ni l'image, ni son asset, ni les résultats existants.
- Une dérivation asset bornée parent→enfant, versionnée par fingerprint, rend
possible l'image de travail DERIVED de `raw.develop` sans confondre SOURCE et
dérivé. La frame vidéo reste future et ne doit pas être présentée comme
importée.
Un contenu identique n'implique pas une identité logique unique. Dans un même
ScanSet, le couple `(scanset_id, asset_id)` est unique : réimporter le même
contenu retourne `ALREADY_PRESENT`. Dans deux ScanSets distincts, deux images
logiques possèdent deux `image_id`, mais peuvent partager le même `asset_id` et
le même fichier physique.
Un ScanSet peut documenter un objet complet, une sous-zone ou une pièce
démontée. Aucune pose ni transformation 3D entre ScanSets n'est inventée en v1.
Ces trois identifiants utilisent `AUTOINCREMENT`. Un ID issu d'une transaction
validée ne sera jamais réattribué, même après une future suppression physique.
Cette garantie est nécessaire avant que Feature Store, Visual Index, matches et
tracks ne commencent à les référencer.
## Stockage physique
Les assets image sont content-addressed :
```text
assets/images/<2 premiers hex>/<sha256 hex lowercase>
```
Le hash est un SHA-256 binaire de 32 octets dans SQLite et est encodé par le
programme pour construire le chemin. Le nom utilisateur ne participe jamais au
chemin de stockage. Le calcul et la copie utilisent un tampon fixe de 64 Kio ;
une image entière n'est jamais chargée en mémoire.
La publication crée un temporaire, le synchronise, publie sans écrasement puis
synchronise le répertoire. Un asset existant n'est adopté qu'après vérification
complète de sa taille et de son SHA-256. La transaction SQLite vient ensuite.
Un échec SQLite peut donc laisser un fichier orphelin, mais jamais une ligne
`READY` créée par le chemin métier avant publication.
## Import et reprise
`import.images` persiste désormais `source_path + scanset_id`. Le ScanSet est
immuable pour un `task_id`. L'import parcourt le dossier en streaming et traite
des lots bornés. Sa correction ne dépend pas de l'ordre de `readdir()` : la
présence logique est décidée par `(scanset_id, SHA-256)` dans la base.
Une source externe reste nécessaire tant que la tâche est récupérable. Après
`COMPLETED`, le catalogue et l'asset géré ne dépendent plus de sa présence.
Les anciennes tâches v3 sont rattachées par migration à un ScanSet explicite
nommé `Imports antérieurs à ScanSet v1`. Ce rattachement exprime seulement
l'absence historique de regroupement ; aucune provenance de capture n'est
inventée.
La migration ne transforme pas les lignes de `manifest.tsv` en images v4 : elle
n'a ni hash ni transaction catalogue historique permettant de le faire sans
rejouer les fichiers. Elle pose donc l'indicateur durable
`legacy_image_catalog_pending=1` lorsqu'une ancienne tâche d'import existe.
- Si une tâche v3 récupérable retrouve sa source, sa reprise relit les sources,
publie les assets content-addressed et remplit le catalogue. Les anciennes
copies sous `images/originals` ne sont ni écrasées ni supprimées.
- Si sa source a disparu, la reconstruction échoue proprement et les anciennes
données restent uniquement legacy ; aucune provenance ni image v4 n'est
inventée.
- Un import v3 déjà terminal n'est pas rejoué automatiquement. Ses images du
manifeste restent accessibles par la projection legacy mais sont marquées
conceptuellement **LEGACY DATA NOT YET CATALOGUED** via l'indicateur DB.
L'indicateur reste conservateur en v1 et n'est pas effacé automatiquement : une
future commande de migration/reconciliation devra vérifier l'intégralité des
données historiques avant de le lever.
## Accès borné
Les listes de ScanSets et d'images utilisent un curseur par ID et une limite de
1 à 256. Aucun `get_all_images()` persistant n'existe. Des milliers d'images ne
nécessitent donc pas autant de records simultanément en mémoire.
## Transition du manifeste
`images/manifest.tsv` reste pris en charge par l'ancien catalogue en mémoire et
les anciennes API d'import. Le chemin production maintient une projection
best-effort par hardlinks pour que la TUI existante continue d'afficher les
nouvelles images sans seconde copie physique. `project.db + assets/images`
reste toutefois la vérité canonique : le manifeste est legacy et diagnostique,
et n'est plus une condition de reprise. Deux images de ScanSets différents qui
partagent un nom ne peuvent pas toutes deux être représentées dans cette vue
legacy. La migration de la TUI vers les pages SQLite reste donc nécessaire.
## Futures étapes
**NOT_YET_WIRED** — sélection de ScanSet dans la TUI, migration de l'ancienne
vue mémoire, vérification/scrub des assets et réconciliation globale des
orphelins.
**IMPLEMENTED** — Feature Store et Visual Index peuvent relier visuellement
des images de ScanSets différents sans dépendre de leur nom ou ordre.
**IMPLEMENTED** — paires candidates, matching, vérification géométrique et
Track Model / Track Builder v1 peuvent consommer le catalogue et ses assets.
Les primitives géométriques Sparse SfM Gate C et le noyau incrémental
synchrone en mémoire Gate D sont **IMPLEMENTED / PASS**.
**IMPLEMENTED / PASS / FROZEN aux frontières acquises** — BA finale Gate E,
orchestration Sparse SfM Gate F, admission Gate G et Phase H v1. MVS-M1 fournit
la frontière externe bornée OpenMVS, mais pas une publication dense durable ni
le pipeline dense complet. Les relations géométriques entre ScanSets,
vérification/scrub des assets et réconciliation globale restent
`NOT_YET_WIRED` ; ce futur n'annule pas les gates déjà acquises.