lardon3d/docs/architecture/reconstruction_pipeline.md

484 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Pipeline de reconstruction Lardon3D
## Vision
Lardon3D ne doit plus être décrit simplement comme « dossier de photos → objet 3D ». La vision cible est :
> « ensemble progressif d'observations et de contraintes
> → reconstruction géométrique persistante, enrichissable et versionnable »
Chaque ajout d'images, de mesures ou de documents enrichit la reconstruction
existante sans détruire les résultats antérieurs. L'utilisateur peut
interrompre le pipeline à tout instant, consulter l'état courant via le
viewer, puis reprendre ultérieurement exactement où il s'était arrêté.
---
## Étapes du pipeline
### A. Scan Sets / acquisitions
| Aspect | Description |
|--------|-------------|
| **Définition** | Un *scan set* (ou acquisition) regroupe un ensemble d'images capturées dans un contexte donné : même lieu, même session, même objectif de reconstruction. |
| **Enrichissement progressif** | Un scan set peut être alimenté par vagues successives : images initiales, images de relèvement, images de contrôle. Chaque vague est horodatée et traçable. |
| **Identification stable** | Chaque ScanSet reçoit un `scanset_id` SQLite positif qui ne dépend pas de son nom. |
**Statut :** IMPLEMENTED v1 — identité, nom et horodatages persistants. Les
vagues et relations géométriques entre acquisitions restent planifiées.
---
### B. Image Catalog
| Aspect | Description |
|--------|-------------|
| **Identité stable** | Chaque image possède un `image_id` stable, distinct du nom et de l'asset physique. |
| **Provenance** | Le catalogue enregistre le ScanSet, la date d'import, le nom et le chemin source, ainsi que la tâche productrice éventuelle. |
| **Asset** | Le contenu physique est identifié par `asset_id`, SHA-256, taille et chemin géré. Plusieurs images logiques peuvent partager cet asset. |
**Statut :** IMPLEMENTED v1 — catalogue SQLite paginé et assets
content-addressed. Les états Feature/Matching/Reconstruction restent planifiés.
---
### C. Feature Extraction et Feature Store
| Aspect | Description |
|--------|-------------|
| **Extraction** | ORB coarse, SIFT précis et RootSIFT, chacun avec sa tâche durable. |
| **Métadonnées persistantes** | `FeatureSet` logique et `FeatureAsset` physique content-addressed dans ProjectDb v7. |
| **Données numériques massives** | ORB U8×32 v1 et SIFT/RootSIFT F32×128 v2 restent hors SQLite. |
| **Lecture bornée** | Le reader lit au plus 256 features par plage et ne charge pas tout le fichier. |
**Statut :** IMPLEMENTED v1 — extraction, publication atomique, reprise et reader borné.
**Extension v1A :** ORB reste la passe coarse et l'unique entrée du Visual
Index ORB-LSH. SIFT/RootSIFT F32×128 sont des passes précises indépendantes,
suivies d'une consolidation spatiale intra-image qui ne mélange jamais les
descriptors.
---
### D. Visual Index
| Aspect | Description |
|--------|-------------|
| **Choix basé sur le contenu visuel** | Le *visual index* permet de retrouver rapidement les images visuellement proches d'une image donnée, sans comparaison exhaustive. Structure type : vocabulaire visuel inversé ou similarité locality-sensitive hashing. |
| **Pipeline conceptuel** | Extraction de features globales → construction de l'index → requête par similarité → retour des K plus proches voisins. |
| **Proximité temporelle comme signal secondaire** | Lorsque les images portent un horodatage EXIF, la proximité temporelle sert de signal complémentaire au contenu visuel, mais ne remplace jamais l'analyse visuelle. |
**Statut :** IMPLEMENTED v1 — LSH binaire déterministe, segments immuables,
updates incrémentales et query top-K bornée. Le générateur de paires reste planifié.
---
### E. Candidate Pair Generator
| Aspect | Description |
|--------|-------------|
| **Sources de paires candidates** | (1) Visual index : paires visuellement proches. (2) Proximité temporelle. (3) Scan set commun. (4) Géométrie approximative (si GPS/IMU disponible). |
| **Matching coûteux limité** | Le nombre de paires soumises au matching géométrique (étape F) doit être borné. Le candidate generator filtre et classe pour ne garder que les paires les plus prometteuses. |
| **Persistance** | Les paires candidates sont persistées dans la table `candidate_pairs` (Project DB v8). Ordre canonique : `image_id_a < image_id_b`. Self-pairs interdits. Unicité garantie. |
| **Déterminisme** | Pour mêmes entrées et configuration, le générateur produit les mêmes paires dans le même ordre. |
| **Idempotence** | L'exécution répétée ne crée pas de doublons. |
**Statut :** IMPLEMENTED v1 — génération single-source et batch depuis Visual
Index, persistance, canonicalisation, idempotence et tâche durable.
---
### F. Matching de descripteurs
| Aspect | Description |
|--------|-------------|
| **Matcher v1** | ORB CPU/Vulkan ; SIFT/RootSIFT L2 CPU (Vulkan rejeté) ; k=2 + Lowe ; sans géométrie. |
| **Persistance** | Match Result NO_MATCH/MATCHED et Match File content-addressed validé. |
| **Orchestration** | `matcher.run` traite les Candidate Pairs par pages et lots durables de 1/2/4/8. |
**Statut :** IMPLEMENTED v1 — Matcher, Match Store, reprise idempotente et Task
durable. Le Geometric Verifier consomme désormais ces résultats.
Le Match Result appartient à Project DB v10 et la tâche durable `matcher.run`
à Project DB v11.
### F2. Geometric Verification
Project DB v12 implémente le modèle scientifique persistant. Chaque
résultat appartient à un Match Result `MATCHED`, possède une identité exacte,
un masque compact borné et, si VERIFIED, une matrice fondamentale 3×3 finie.
La publication est atomique et immutable. Project DB v13 ajoute uniquement la
persistance de `geometric_verifier.run` v1. Le calcul Fundamental emploie
USAC/MAGSAC avec configuration, seed et fingerprint déterministes.
**Statut :** IMPLEMENTED v1 — MODEL, VERIFIER ET TASK DURABLE.
---
### G. Tracks
| Aspect | Description |
|--------|-------------|
| **Observation 2D → track → point3D** | Un *track* est une chaîne d'observations 2D cohérentes d'un même point 3D à travers plusieurs images. Chaque observation est un keypoint indexé par image. |
| **Lien avec le catalogue** | Les tracks référenceront les images par `image_id`, jamais par nom ou chemin. |
| **Persistance** | Les tracks sont persistés entre les sessions de traitement. Un track ne peut être détruit que par une action explicite de l'utilisateur. |
**Statut :** COMPLETED/FROZEN — le Track Builder v1 direct et durable est
implémenté dans Project DB v15 (`track_sets`, `tracks`, `track_observations` et
le payload de tâche). Les primitives de géométrie calibrée Gate C sont
implémentées et le noyau Sparse SfM incrémental Gate D est IMPLEMENTED / PASS ;
le Bundle Adjustment final Gate E est PASS / FROZEN ; l'orchestration projet
et tâche Gate F est PASS / FROZEN.
Le modèle de persistance Sparse SfM v16 est gelé après Gate B.
**Sparse SfM Gate A : PASS.** Le contrat géométrique, la stratégie
incremental, la triangulation candidate, le gauge, les conventions de pose,
les limites BA et l'enveloppe matérielle sont documentés dans
`architecture/sparse_sfm.md`. Ses primitives pures calibrées Gate C sont
**IMPLEMENTED / PASS** et son noyau incrémental synchrone en mémoire Gate D est
**IMPLEMENTED / PASS**. Le Bundle Adjustment final par composante Gate E est
**PASS / FROZEN**. L'orchestration Gate F est **PASS / FROZEN**. Le modèle de
reconstruction v16 et ses lecteurs bornés restent ceux de B2 ; le payload de
tâche Gate F est l'ajout v17.
---
### H. Reconstruction incrémentale
La phase H est une étape du pipeline, pas un « Gate H ». Sa version 1 enrichit
scientifiquement un snapshot Sparse SfM publié, sans réexécuter Gate F depuis
zéro. Ses entrées immuables sont l'identité de la reconstruction de base,
l'identité d'un Track Set d'extension et l'identité d'un scope de calibration.
Son identité scientifique est :
```
(base_reconstruction_identity, extension_track_set_identity,
calibration_scope_identity, incremental_kind, incremental_version,
parameter_fingerprint)
```
L'enregistrement canonique `L3DHIDV1` est encodé little-endian puis condensé
par SHA-256. Le fingerprint H v1, distinct de F0, encode `L3DHPRM1`, sa version
et les politiques versionnées suivantes : filiation stricte par identité
d'observation `(feature_set_id, feature_index)`, absence de split/merge,
`STRICT_DESCENDANT_REGISTERED_OBSERVATIONS_V1`,
`PRESERVE_BASE_COMPONENT_KEY_V1`, absence de fusion de composantes de base,
politiques scientifiques Gate D v1 pour PnP/triangulation/raffinement, BA
complète Gate E v1 de chaque composante affectée, calibration historique
exacte, préservation du gauge historique, snapshot complet immuable,
ordre canonique et absence de nouvelle composante déconnectée. Matériel,
ressources, tâche, chemins, horodatages et télémétrie sont exclus.
L'enregistrement de fingerprint fait exactement 80 octets ; son vecteur doré
SHA-256 v1 est
`f44a89b23b520481701848ecf175638e82f2c9e25b70a01f3eb767bf28446cd8`.
Après le magic et les deux versions `u32`, ses seize champs `u32` versionnés
sont, dans l'ordre : `STRICT_OBSERVATION_LINEAGE`, `NO_SPLIT`, `NO_MERGE`,
`STRICT_DESCENDANT_REGISTERED_OBSERVATIONS`, `PRESERVE_BASE_COMPONENT_KEY`,
`NO_BASE_COMPONENT_MERGE`, `GATE_D_PNP_REGISTRATION`,
`GATE_D_TRIANGULATION_REFINEMENT`, `FULL_AFFECTED_COMPONENT_BA`,
`GATE_E_POLICY`, `EXACT_HISTORICAL_CALIBRATION`, `HISTORICAL_BASE_GAUGE`,
`COMPLETE_IMMUTABLE_SNAPSHOT`, `CANONICAL_ID_ORDER`,
`NO_DISCONNECTED_COMPONENT` et `TERMINAL_BA_FAILURE`. Chaque valeur vaut 1
en H v1 ; la position identifie la politique sans chaîne dépendante de locale.
La filiation est dérivée par indexation déterministe des observations, jamais
par égalité de `track_id` entre générations. Chaque Track historique publié a
un unique descendant contenant exactement toutes ses observations historiques.
Un historique manquant, dupliqué, scindé, fusionné ou reliant deux composantes
de base invalide toute la mise à jour. Une observation ajoutée à un descendant
historique dont l'image nouvelle reste non enregistrée dans sa composante
invalide également toute la mise à jour ; une image non enregistrée qui ne
porte que des Tracks nouveaux reste admissible et ne crée pas de composante.
Chaque caméra nouvelle est localisée dans exactement une composante historique.
Les composantes de base ne fusionnent pas et gardent leur `component_key`, égal
au plus petit `image_id` historique enregistré. Une caméra géométriquement
enregistrée dont l'ID est inférieur à cette clé rend la mise à jour
incompatible ; elle n'est pas maquillée en rejet géométrique. Les landmarks
existants reçoivent leurs observations descendantes admissibles, les Tracks
nouveaux entièrement ancrés peuvent être triangulés, puis chaque composante
affectée subit une BA complète avec les ancres historiques. Une composante non
affectée est copiée sans modification scientifique.
La sortie est un snapshot complet, autonome et immuable. Le prédécesseur est
une provenance, jamais une dépendance de lecture en chaîne. Publication et
métadonnées H sont atomiques ; toute erreur de filiation, calibration,
compatibilité de clé, géométrie, BA, annulation ou base de données ne publie
rien et ne modifie pas le prédécesseur. Une identité H déjà publiée est
réutilisée avant matérialisation lourde. Une extension sans effet scientifique
retourne explicitement le prédécesseur sans publier une génération factice.
Les métriques globales sont recalculées après la BA sur chaque observation
finalement retenue, avec la projection pixel Gate F. Une profondeur invalide,
un résidu non fini ou un ensemble vide rend la publication impossible.
L'API scientifique publique valide aussi la cohérence complète du snapshot de
base : landmark, Track, caméra, composante et position canonique de chaque
observation doivent correspondre. Son résultat doit être initialisé à zéro ou
avoir été préalablement détruit avant un nouvel appel.
L'orchestration appartient à la tâche durable `incremental_reconstruction.run`
v1, atomique (lot 1..1), sans DAG ni état scientifique intermédiaire durable.
Après redémarrage elle recommence depuis les entrées immuables. Son estimation
opérationnelle H v1 est, avec arithmétique `uint64_t` vérifiée et arrondi au MiB
supérieur :
```
raw = 268435456
+ base_cameras * 131072
+ base_landmarks * 4096
+ base_observations * 1024
+ extension_images * 131072
+ extension_tracks * 4096
+ extension_observations * 1024
```
Elle est CPU, fixe, `min_batch_size=max_batch_size=1`, demande un thread CPU,
zéro slot GPU et un slot IO. Elle est persistée avec la tâche, exclue de
l'identité et admise uniquement par Queue → Governor → Reservation. Pression
mémoire et ressources ne modifient jamais la science.
Project DB v18 ajoute seulement les métadonnées d'identité/prédécesseur H et
le payload de tâche H. Le parent générique conserve le vrai fingerprint de
paramètres et porte l'identité H dans son `derivation_identity` nullable ; les
lignes Gate F gardent ce champ à `NULL` et leur unicité historique. La
géométrie reste un snapshot complet conforme au
modèle historique ; aucun sous-ensemble d'observations, remappage de clé ou
graphe de filiation générique n'est introduit.
**Statut :** PASS / FROZEN — contrat H v1 implémenté et validé. Validation
finale : suite normale 42/42 PASS ; suite ciblée ASan/UBSan/LSan 5/5 PASS ;
revue finale indépendante PASS ; `git diff --check` PASS. Restent différés :
généalogie complexe, split/merge légitime,
fusion de composantes, BA locale, DAG, étapes scientifiques intermédiaires
durables, scratch/SSD, GPU SfM/BA et nouvelles composantes déconnectées.
---
### MVS-M1. Dense externe borné
MVS-M1 est **IMPLEMENTED / VALIDATION PENDING**. Son identité scientifique lie
l'identité de reconstruction de base, l'identité du jeu d'images source, le
`calibration_scope_identity` historique, le binding numérique de calibration MVS,
le backend et les paramètres. Les octets des sources restent liés séparément par
leur SHA-256 ; ils ne sont pas remplacés par le binding de calibration. Le record
d'identité dense est `L3DMDID2`, version 2, de taille fixe 220 octets. Sa frontière
externe est OpenMVS v2.4.0 : Sparse/H est exporté vers COLMAP, puis traité par
`InterfaceCOLMAP` et `DensifyPointCloud`.
Le binding numérique de calibration est le record `L3DMCAL1`, version 1. Il est
formé après tri strict des `image_id` et rejet des identifiants dupliqués ; tous
les entiers et binary64 y sont encodés explicitement en little-endian de largeur
fixe. Les NaN et infinis sont rejetés et toute valeur `-0` est canonisée en `+0`
avant l'encodage. Il lie, pour chaque image, son identifiant, ses dimensions et
les paramètres numériques de calibration utilisés par l'undistortion.
L'export COLMAP est déterministe et écrit en flux : locale classique, notation
scientifique et précision binary64 fidèle au round-trip (`max_digits10 - 1`
chiffres après le point). Les images et les observations source sont undistordues
de façon déterministe avec OpenCV ; les observations exportées sont les
coordonnées transformées. `points3D.txt` contient les tracks COLMAP réels,
produits dans l'ordre déterministe à partir d'un index de landmarks et de leurs
observations : l'export ne rescane pas quadratiquement les observations pour
chaque landmark.
Chaque invocation crée sous le staging fourni par l'appelant un espace de travail
privé neuf. Aucune scène, profondeur, cache ou sortie d'une invocation antérieure
n'est recherchée ni réemployée. Les images undistordues et tous les intermédiaires
COLMAP/OpenMVS restent dans cet espace de travail.
Le résultat accepté est le PLY fusionné ; `fusion_mode` doit être 0. Le lecteur
valide le format de sortie par défaut d'OpenMVS v2.4.0 : PLY binaire
little-endian. Le plafond d'en-tête est de 1 MiB et compte les octets bruts :
une fin de ligne CRLF consomme donc deux octets, contrairement à LF. Les règles
de lignes acceptent LF et CRLF ; un CR seul malformé est rejeté. Chaque ligne
est bornée à 64 KiB. Le lecteur borne les listes de propriétés `view_indices` et
`view_weights` avant
allocation ou lecture de leur contenu, et valide les valeurs numériques sans
dépendre de la locale du processus.
La capacité CPU-only est conditionnelle : le probe borné interroge chaque
exécutable backend, vérifie qu'il s'identifie comme OpenMVS v2.4.0 et relève les
options effectivement exposées. Si `DensifyPointCloud` expose `--cuda-device`,
l'adaptateur demande explicitement le périphérique CPU ; sinon il n'ajoute aucune
option CUDA. Les options requises, dont `--max-threads`, doivent être exposées ;
celle-ci reçoit le nombre de threads d'exécution. Ce nombre est opérationnel et
exclu de l'identité scientifique.
Les contrats B1B6 du jalon sont les suivants :
- **B1 — result failure atomicity.** Un échec ne retourne aucun
résultat MVS-M1 partiel.
- **B2 — source-image status classification.** Les erreurs source,
snapshot, I/O, backend, output et OOM restent distinctes.
- **B3 — source byte identity binding.** Chaque source acceptée est
un fichier régulier dont la taille du descripteur est contrôlée avant le hash ;
son SHA-256 complet est calculé avec une mémoire de streaming fixe, jusqu'à
1 GiB au maximum par fichier source, puis la stabilité du fichier est vérifiée
après lecture. Il n'existe volontairement aucun budget agrégé de 1 GiB pour le
jeu de sources : la vérification totale croît avec les octets d'entrée acceptés
et son admission/comptage global relève d'un futur Task/Governor.
- **B4 — deterministic COLMAP numeric export.** Les images, observations et
tracks sont ceux décrits ci-dessus ; aucune observation distordue n'est exportée
à la place de sa coordonnée transformée, et l'export des tracks ne réalise pas
de rescan quadratique landmark-observation. Une coordonnée source undistordue
non finie est `INVALID_SOURCE_IMAGE`, tandis qu'une pose du snapshot invalide
reste `INVALID_SNAPSHOT`.
- **B5 — write/close FD cleanup.** Les descripteurs possédés sont nettoyés
explicitement lors de l'écriture et de la fermeture,
y compris sur erreur.
- **B6 — C ABI / `std::bad_alloc` containment.** L'API C ne laisse pas
traverser `std::bad_alloc`.
Indépendamment de la borne de ligne PLY, l'inspection de l'en-tête d'image
encodée avant décodage est bornée à 64 KiB. Les formats acceptés implémentés sont
PNG, JPEG et BMP ; les dimensions, le nombre de pixels et l'estimation de working
set sont validés avant décodage (16 384 pixels par dimension, 40 Mpx et 240 MB).
Les intermédiaires d'undistortion déterministe
et les intermédiaires COLMAP/OpenMVS sont privés à l'espace de travail neuf créé
par invocation sous le staging appelant ; aucune invocation ne réemploie des
artefacts antérieurs.
Les deux exécutables backend sont hashés sous un budget borné partagé, distinct
du plafond par fichier des sources. Les processus possédés et leurs groupes sont
nettoyés ; stdout et stderr sont intégralement drainés pour chaque backend, avec
au plus 1 MiB retenu par log. L'excédent est consommé puis écarté, sans empêcher
le backend de poursuivre et de réussir.
Restent différés : publication dense dans la Project DB, Task Runtime,
Queue/Governor, `ResourceEstimate`, annulation, publication dense durable,
mesh, texturing, viewer, scratch/SSD et infrastructure backend généralisée.
Ce jalon ne marque ni MVS complet ni PASS / FROZEN.
---
### I. Reconstruction Layers
| Aspect | Description |
|--------|-------------|
| **Conservation de la provenance** | Chaque point 3D, chaque caméra, chaque track conserve la trace de son origine : quel scan set, quelle vague, quelle session. |
| **Consolidation distincte** | La fusion de layers (consolidation) est un processus séparé de l'ajout de données. L'utilisateur décide quand consolider. La consolidation ne détruit pas les layers d'origine. |
**Statut :** PLANNED — aucune implémentation existante.
---
### J. Sources géométriques externes
| Aspect | Description |
|--------|-------------|
| **PHOTO** | Images du scan set (source principale). |
| **MEASUREMENT** | Mesures directes : distances, orientations, coordonnées GPS, nuages de points LiDAR. Intègrent le graphe de contraintes comme edges géométriques supplémentaires. |
| **DOCUMENT** | Plans, relevés, fiches techniques. Métadonnées contextuelles qui enrichissent le projet sans contribuer directement au calcul géométrique. |
**Statut :** PLANNED — aucune implémentation existante.
---
### K. Viewer intégré
| Aspect | Description |
|--------|-------------|
| **Consommateur passif** | Le viewer ne calcule jamais. Il lit les snapshots validés publiés par le pipeline et les affiche. |
| **Isolation fonctionnelle** | Le viewer s'exécute dans un thread dédié ou un processus séparé. Il ne partage aucun buffer mutable avec les workers de calcul. |
| **Reactive** | L'utilisateur voit la reconstruction apparaître progressivement pendant les calculs, sans attendre la fin de l'étape courante. |
**Statut :** PLANNED — le viewer Vulkan séparé n'est pas encore commencé.
---
## Invariants
Ces invariants s'appliquent à toutes les étapes du pipeline :
1. **Chaque étape est indépendante et reprenable.**
L'exécution peut être interrompue à n'importe quelle frontière de lot
et reprise sans perte de données.
2. **Résultats atomiques et validés uniquement.**
Un résultat n'est publié (rendu visible aux étapes suivantes et au
viewer) que lorsqu'il est entièrement calculé, vérifié et persisté.
3. **Pas de destruction silencieuse des données sources.**
Les images originales, les features extraites, les tracks et les
points 3D existants ne jamais supprimés implicitement. Toute
suppression est une action explicite et traçable.
4. **Le scheduler ne décide jamais des ressources.**
Seul le Resource Governor arbitre les budgets, les lots et les
réservations.
5. **Les réservations sont obligatoires.**
Aucune tâche ne s'exécute sans réservation active préalablement
accordée par le governor.
6. **ncurses appartient exclusivement au thread principal.**
Aucun worker ne touche à l'interface TUI.
---
## Diagramme conceptuel
```
Scan Sets (A)
Image Catalog (B) ──► Feature Store (C)
Visual Index (D)
Candidate Pair Generator (E)
Matching / Geometric Verification (F)
Tracks (G)
Reconstruction Incrémentale (H)
│ │
▼ ▼
Reconstruction Sources
Layers (I) Externes (J)
│ │
└────┬─────┘
Viewer Intégré (K)
```
---
## Statut du pipeline
Import, Image Catalog, Feature Extraction, Feature Store, Visual Index,
Candidate Pair, Matching v1, Geometric Verification, Track Model/Builder v1
and Sparse SfM Gate C geometry are **IMPLEMENTED**. The synchronous in-memory
incremental Sparse SfM Gate D core is **IMPLEMENTED / PASS**, and final
per-component Gate E BA is **PASS / FROZEN**. Gate F orchestration and Gate G
Governor integration are **PASS / FROZEN**. Phase H v1 is **PASS / FROZEN**.
MVS-M1 is **IMPLEMENTED / VALIDATION PENDING**: `L3DMDID2` v2 (220-byte) dense
identity binds the base reconstruction, source-image-set,
historical `calibration_scope_identity`, exact `L3DMCAL1` v1 numeric calibration
binding, backend and parameters, while source bytes remain separately bound;
deterministic OpenCV-undistorted COLMAP images, transformed observations and real
tracks exported from an index without a quadratic landmark-observation rescan;
and the external OpenMVS v2.4.0
`InterfaceCOLMAP`/`DensifyPointCloud` boundary. Every invocation has a fresh
private workspace beneath caller staging and reuses no prior scene, depth, cache
or output. Its numeric calibration binding sorts image IDs, uses explicit
fixed-width little-endian fields, rejects NaN/Inf and canonicalizes `-0`; PLY
headers are capped at 1 MiB of raw bytes (so CRLF costs two), accept LF/CRLF,
reject malformed bare CR, and cap each line at 64 KiB.
Non-finite undistorted source coordinates are `INVALID_SOURCE_IMAGE`; invalid
snapshot poses remain `INVALID_SNAPSHOT`. It validates OpenMVS's default binary
little-endian PLY, including bounded `view_indices`/`view_weights` lists with
locale-independent numeric validation, requires fusion mode 0 for the fused PLY,
and has conditional CPU-only capability plus supported `--max-threads`. It does
not include durable dense publication, runtime/governor integration, mesh,
texturing or viewer; those remain **PLANNED**.
Ce document décrit la vision architecturale cible du pipeline de
reconstruction. Les modules listés ici ne sont pas tous implémentés.
L'implémentation suit la feuille de route canonique de `docs/roadmap/roadmap.md`
et progresse par phases respectant les invariants d'indépendance et de reprise.