feat: complete incremental reconstruction phase H
This commit is contained in:
parent
070280b9cc
commit
138b13babc
31 changed files with 3708 additions and 118 deletions
|
|
@ -142,6 +142,13 @@ et à la publication Project DB v17 atomique et idempotente.
|
||||||
|
|
||||||
**Statut :** GATE F — PASS / FROZEN. Gate G est **PASS / FROZEN**.
|
**Statut :** GATE F — PASS / FROZEN. Gate G est **PASS / FROZEN**.
|
||||||
|
|
||||||
|
La phase de pipeline H `incremental_reconstruction.run` v1 enrichit un snapshot
|
||||||
|
publié et produit un nouveau snapshot complet avec provenance prédécesseur dans
|
||||||
|
Project DB v18. Elle reste scientifiquement distincte de Gate F et ne modifie
|
||||||
|
ni F0 ni les contrats Gate D/E.
|
||||||
|
|
||||||
|
**Statut :** PHASE H V1 — PASS / FROZEN.
|
||||||
|
|
||||||
## Résultats et publication live
|
## Résultats et publication live
|
||||||
|
|
||||||
Les traitements fonctionnent par séquences adaptatives : lire un lot borné,
|
Les traitements fonctionnent par séquences adaptatives : lire un lot borné,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,33 @@
|
||||||
# Base de données projet Lardon3D
|
# Base de données projet Lardon3D
|
||||||
|
|
||||||
## Gate F — décision Project DB v17
|
## Phase H — décision Project DB v18
|
||||||
|
|
||||||
|
**PASS / FROZEN.** La migration transactionnelle v17→v18
|
||||||
|
ajoute `derivation_identity` au parent `sparse_reconstructions`, puis crée
|
||||||
|
`incremental_reconstructions` et `incremental_reconstruction_tasks`.
|
||||||
|
Les lignes Gate F ont `derivation_identity IS NULL` et gardent exactement leur
|
||||||
|
`parameter_fingerprint`. Leur tuple candidat historique est imposé par un
|
||||||
|
index UNIQUE partiel. Les lignes dérivées H stockent le vrai fingerprint H
|
||||||
|
dans `parameter_fingerprint` et l'identité scientifique H canonique dans
|
||||||
|
`derivation_identity`, elle-même protégée par un second index UNIQUE partiel.
|
||||||
|
La reconstruction transactionnelle du parent et de ses tables filles conserve
|
||||||
|
les IDs, lignes, clés étrangères et cascades v17 sans réécrire les données
|
||||||
|
scientifiques.
|
||||||
|
La première table associe atomiquement un snapshot Sparse SfM complet à son
|
||||||
|
prédécesseur, au Track Set d'extension, au scope de calibration, au kind/version
|
||||||
|
H, au fingerprint H et à son identité scientifique SHA-256 unique. La seconde
|
||||||
|
conserve le payload immuable de la tâche durable H. Aucun état de réservation,
|
||||||
|
snapshot de ressources, géométrie partielle, sous-ensemble d'observations,
|
||||||
|
remappage de composante ou graphe de filiation n'est persisté. Un redémarrage
|
||||||
|
recalcule depuis les entrées immuables.
|
||||||
|
|
||||||
|
La publication H réutilise sans le relâcher le validateur structurel historique
|
||||||
|
du snapshot complet. Géométrie et métadonnées H sont insérées dans une même
|
||||||
|
transaction ; une erreur tardive de métadonnées annule aussi toute la nouvelle
|
||||||
|
géométrie. Les lignes Gate F v16/v17 restent inchangées et leurs recherches
|
||||||
|
exactes conservent leur sémantique.
|
||||||
|
|
||||||
|
## Gate F — décision historique Project DB v17
|
||||||
|
|
||||||
**PASS / FROZEN.** La migration historique v15→v16 et le
|
**PASS / FROZEN.** La migration historique v15→v16 et le
|
||||||
modèle de reconstruction Sparse SfM v16 restent inchangés. Gate F fait avancer
|
modèle de reconstruction Sparse SfM v16 restent inchangés. Gate F fait avancer
|
||||||
|
|
@ -38,7 +65,9 @@ persistée exactement ; seule une composante de graphe non reconstruite est
|
||||||
omise. Les comptes et métriques globaux décrivent exclusivement la géométrie
|
omise. Les comptes et métriques globaux décrivent exclusivement la géométrie
|
||||||
effectivement persistée. Aucun placeholder ni table diagnostique n'est ajouté.
|
effectivement persistée. Aucun placeholder ni table diagnostique n'est ajouté.
|
||||||
|
|
||||||
> Version courante : **v17**. La migration transactionnelle v16→v17 ajoute le
|
> Version courante : **v18**. La migration transactionnelle v17→v18 ajoute le
|
||||||
|
> discriminateur générique nullable et les deux tables H minimales décrites
|
||||||
|
> ci-dessus. La migration v16→v17 ajoute le
|
||||||
> payload durable typé `sparse_sfm_tasks`. La migration transactionnelle v15→v16 ajoute
|
> payload durable typé `sparse_sfm_tasks`. La migration transactionnelle v15→v16 ajoute
|
||||||
> le modèle persistant Sparse SfM (calibrations, scopes et reconstructions).
|
> le modèle persistant Sparse SfM (calibrations, scopes et reconstructions).
|
||||||
> La migration transactionnelle v14→v15 ajoute
|
> La migration transactionnelle v14→v15 ajoute
|
||||||
|
|
@ -779,8 +808,8 @@ ouvert.
|
||||||
|
|
||||||
## Statut
|
## Statut
|
||||||
|
|
||||||
**IMPLEMENTED** — SQLite système, schéma v15 et migrations séquentielles
|
**IMPLEMENTED** — SQLite système, schéma v18 et migrations séquentielles
|
||||||
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14→v15, identité projet, transactions
|
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12→v13→v14→v15→v16→v17→v18, identité projet, transactions
|
||||||
tâche+checkpoint, pagination de reprise et artefacts génériques.
|
tâche+checkpoint, pagination de reprise et artefacts génériques.
|
||||||
|
|
||||||
**IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente,
|
**IMPLEMENTED** — ouverture/fermeture avec le projet, identité INI/DB cohérente,
|
||||||
|
|
@ -837,7 +866,7 @@ orphelins et compaction Visual Index. Feature Store et Visual Index v1 sont impl
|
||||||
Visual Index v1 borne un index à 256 segments de 16 memberships, soit 4096 Feature Sets;
|
Visual Index v1 borne un index à 256 segments de 16 memberships, soit 4096 Feature Sets;
|
||||||
la couverture de 50 000 Feature Sets nécessitera la compaction ou une évolution v2.
|
la couverture de 50 000 Feature Sets nécessitera la compaction ou une évolution v2.
|
||||||
|
|
||||||
**PLANNED** — dépendances d'artefacts, graphe géométrique et orchestration
|
**PASS / FROZEN** — Phase H v1 : métadonnées d'identité et
|
||||||
projet/tâche de la reconstruction incrémentale. Le noyau Sparse SfM
|
de prédécesseur dans `incremental_reconstructions`, payload durable dans
|
||||||
incrémental synchrone en mémoire Gate D est **IMPLEMENTED / PASS** hors
|
`incremental_reconstruction_tasks`, snapshot complet publié atomiquement et
|
||||||
Project DB v16.
|
réutilisation par identité H. Aucun graphe de dépendances n'est introduit.
|
||||||
|
|
|
||||||
|
|
@ -144,13 +144,112 @@ tâche Gate F est l'ajout v17.
|
||||||
|
|
||||||
### H. Reconstruction incrémentale
|
### H. Reconstruction incrémentale
|
||||||
|
|
||||||
| Aspect | Description |
|
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
|
||||||
| **Indexation** | À chaque vague d'images, la reconstruction existante est indexée (positions approximatives des points 3D, orientations des caméras). |
|
zéro. Ses entrées immuables sont l'identité de la reconstruction de base,
|
||||||
| **Comparaison aux acquisitions précédentes** | Les nouvelles images sont comparées à la reconstruction existante : localisation des caméras, triangulation de nouveaux points, mise à jour des tracks existants. |
|
l'identité d'un Track Set d'extension et l'identité d'un scope de calibration.
|
||||||
| **Enrichissement local** | Seules les régions couvertes par les nouvelles images sont recalculées. Le reste de la reconstruction reste inchangé et valide. |
|
Son identité scientifique est :
|
||||||
|
|
||||||
**Statut :** PLANNED — aucune implémentation existante.
|
```
|
||||||
|
(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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -259,12 +358,11 @@ Import, Image Catalog, Feature Extraction, Feature Store, Visual Index,
|
||||||
Candidate Pair, Matching v1, Geometric Verification, Track Model/Builder v1
|
Candidate Pair, Matching v1, Geometric Verification, Track Model/Builder v1
|
||||||
and Sparse SfM Gate C geometry are **IMPLEMENTED**. The synchronous in-memory
|
and Sparse SfM Gate C geometry are **IMPLEMENTED**. The synchronous in-memory
|
||||||
incremental Sparse SfM Gate D core is **IMPLEMENTED / PASS**, and final
|
incremental Sparse SfM Gate D core is **IMPLEMENTED / PASS**, and final
|
||||||
per-component Gate E BA is **PASS / FROZEN**. Gate F orchestration is
|
per-component Gate E BA is **PASS / FROZEN**. Gate F orchestration and Gate G
|
||||||
**PASS / FROZEN**. Gate G Governor integration, MVS, mesh,
|
Governor integration are **PASS / FROZEN**. Phase H v1 is **PASS / FROZEN**.
|
||||||
texturing and viewer remain **PLANNED**.
|
MVS, mesh, texturing and viewer remain **PLANNED**.
|
||||||
|
|
||||||
Ce document décrit la vision architecturale cible du pipeline de
|
Ce document décrit la vision architecturale cible du pipeline de
|
||||||
reconstruction. Les modules listés ici ne sont pas tous implémentés.
|
reconstruction. Les modules listés ici ne sont pas tous implémentés.
|
||||||
L'implémentation suit la feuille de route définie dans `.opencode/context.md`
|
L'implémentation suit la feuille de route canonique de `docs/roadmap/roadmap.md`
|
||||||
et progresse par tickets successifs en respectant les invariants
|
et progresse par phases respectant les invariants d'indépendance et de reprise.
|
||||||
d'indépendance et de reprise.
|
|
||||||
|
|
|
||||||
|
|
@ -84,3 +84,10 @@ version, fingerprint, checksum, tri, unicité ou L3DTSIS1 incohérents.
|
||||||
payload scientifique explicite Project DB v17, restaure l'estimation générique
|
payload scientifique explicite Project DB v17, restaure l'estimation générique
|
||||||
persistée et rejoue D puis E depuis les références Track Set/calibration. Le
|
persistée et rejoue D puis E depuis les références Track Set/calibration. Le
|
||||||
fingerprint F0 est recalculé ; le checkpoint générique v1 reste inchangé.
|
fingerprint F0 est recalculé ; le checkpoint générique v1 reste inchangé.
|
||||||
|
|
||||||
|
**PASS / FROZEN** — `incremental_reconstruction.run`,
|
||||||
|
version 1, recharge le payload Project DB v18 composé du prédécesseur, du Track
|
||||||
|
Set d'extension, du scope de calibration et du fingerprint H. La tâche atomique
|
||||||
|
recalcule depuis ces entrées après redémarrage, passe par la Queue et le
|
||||||
|
Governor avec son estimation H immuable, et ne persiste aucun état de solveur.
|
||||||
|
Elle n'ajoute ni DAG ni dépendance implicite.
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ Lardon3D suit une feuille de route ordonnée qui privilégie la stabilité et la
|
||||||
- ✅ Sparse SfM Gate F : orchestration durable et publication atomique PASS / FROZEN
|
- ✅ Sparse SfM Gate F : orchestration durable et publication atomique PASS / FROZEN
|
||||||
- ✅ Sparse SfM Gate G : politique et cœur **PASS / FROZEN**
|
- ✅ Sparse SfM Gate G : politique et cœur **PASS / FROZEN**
|
||||||
|
|
||||||
### Phase 5 : Reconstruction (PLANNED)
|
### Phase 5 : Reconstruction
|
||||||
- 📋 Orchestration de reconstruction incrémentale
|
- ✅ Orchestration de reconstruction incrémentale H v1 — **PASS / FROZEN**
|
||||||
- 📋 MVS / dense
|
- 📋 MVS / dense
|
||||||
- 📋 Mesh
|
- 📋 Mesh
|
||||||
- 📋 Contraintes externes
|
- 📋 Contraintes externes
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ typedef struct Lardon3DResourceGovernor Lardon3DResourceGovernor;
|
||||||
typedef struct Lardon3DProjectDb Lardon3DProjectDb;
|
typedef struct Lardon3DProjectDb Lardon3DProjectDb;
|
||||||
typedef struct Lardon3DOrbVulkanBackend Lardon3DOrbVulkanBackend;
|
typedef struct Lardon3DOrbVulkanBackend Lardon3DOrbVulkanBackend;
|
||||||
|
|
||||||
|
enum { LARDON3D_APP_STATE_PATH_CAPACITY = 4096 };
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LARDON3D_SCREEN_HOME = 0,
|
LARDON3D_SCREEN_HOME = 0,
|
||||||
LARDON3D_SCREEN_PROJECTS,
|
LARDON3D_SCREEN_PROJECTS,
|
||||||
|
|
@ -22,12 +24,12 @@ typedef enum {
|
||||||
LARDON3D_SCREEN_RESOURCES
|
LARDON3D_SCREEN_RESOURCES
|
||||||
} Lardon3DScreen;
|
} Lardon3DScreen;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct Lardon3DAppState {
|
||||||
Lardon3DScreen screen;
|
Lardon3DScreen screen;
|
||||||
bool running;
|
bool running;
|
||||||
bool project_loaded;
|
bool project_loaded;
|
||||||
char project_name[128];
|
char project_name[128];
|
||||||
char project_path[PATH_MAX];
|
char project_path[LARDON3D_APP_STATE_PATH_CAPACITY];
|
||||||
char project_stable_id[65];
|
char project_stable_id[65];
|
||||||
char status_message[256];
|
char status_message[256];
|
||||||
Lardon3DImageCatalog *image_catalog;
|
Lardon3DImageCatalog *image_catalog;
|
||||||
|
|
|
||||||
102
include/lardon3d/incremental_reconstruction.h
Normal file
102
include/lardon3d/incremental_reconstruction.h
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
#ifndef LARDON3D_INCREMENTAL_RECONSTRUCTION_H
|
||||||
|
#define LARDON3D_INCREMENTAL_RECONSTRUCTION_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <lardon3d/resource_governor.h>
|
||||||
|
#include <lardon3d/sparse_sfm_incremental.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_KIND = 1,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_VERSION = 1,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_SHA256_SIZE = 32,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_FINGERPRINT_RECORD_SIZE = 80,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_IDENTITY_RECORD_SIZE = 76,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_OK = 0,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_NO_CHANGE,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_ARGUMENT,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_OUT_OF_MEMORY,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MISSING,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_SPLIT,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MERGE,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_DUPLICATE,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_CROSS_COMPONENT_BRIDGE,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_DESCENDANT_UNREGISTERED_OBSERVATION,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_COMPONENT_KEY_VIOLATION,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_GEOMETRY_FAILED,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_BUNDLE_ADJUSTMENT_FAILED,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_CANCELLED,
|
||||||
|
} Lardon3DIncrementalReconstructionStatus;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t base_reconstruction_id;
|
||||||
|
uint64_t extension_track_set_id;
|
||||||
|
uint64_t calibration_scope_id;
|
||||||
|
uint32_t incremental_kind;
|
||||||
|
uint32_t incremental_version;
|
||||||
|
unsigned char parameter_fingerprint[32];
|
||||||
|
} Lardon3DIncrementalReconstructionIdentity;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t base_camera_count;
|
||||||
|
uint64_t base_landmark_count;
|
||||||
|
uint64_t base_observation_count;
|
||||||
|
uint64_t extension_image_count;
|
||||||
|
uint64_t extension_track_count;
|
||||||
|
uint64_t extension_observation_count;
|
||||||
|
} Lardon3DIncrementalReconstructionShape;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t base_reconstruction_id;
|
||||||
|
const Lardon3DSparseIncrementalResult *base;
|
||||||
|
const Lardon3DSparseIncrementalObservation *base_track_observations;
|
||||||
|
size_t base_track_observation_count;
|
||||||
|
const Lardon3DSparseIncrementalInput *extension;
|
||||||
|
const Lardon3DSparseIncrementalParameters *parameters;
|
||||||
|
bool (*checkpoint)(void *context);
|
||||||
|
void *checkpoint_context;
|
||||||
|
} Lardon3DIncrementalReconstructionInput;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Lardon3DIncrementalReconstructionStatus status;
|
||||||
|
bool changed;
|
||||||
|
Lardon3DSparseIncrementalResult snapshot;
|
||||||
|
} Lardon3DIncrementalReconstructionResult;
|
||||||
|
|
||||||
|
bool lardon3d_incremental_reconstruction_parameter_fingerprint(
|
||||||
|
unsigned char digest[32]);
|
||||||
|
bool lardon3d_incremental_reconstruction_parameter_fingerprint_record(
|
||||||
|
unsigned char record[
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_FINGERPRINT_RECORD_SIZE]);
|
||||||
|
bool lardon3d_incremental_reconstruction_identity_record(
|
||||||
|
const Lardon3DIncrementalReconstructionIdentity *identity,
|
||||||
|
unsigned char record[LARDON3D_INCREMENTAL_RECONSTRUCTION_IDENTITY_RECORD_SIZE]);
|
||||||
|
bool lardon3d_incremental_reconstruction_identity_digest(
|
||||||
|
const Lardon3DIncrementalReconstructionIdentity *identity,
|
||||||
|
unsigned char digest[32]);
|
||||||
|
bool lardon3d_incremental_reconstruction_resource_estimate(
|
||||||
|
const Lardon3DIncrementalReconstructionShape *shape,
|
||||||
|
Lardon3DResourceEstimate *estimate);
|
||||||
|
|
||||||
|
/* result must be zero-initialized or previously destroyed before run(). */
|
||||||
|
Lardon3DIncrementalReconstructionStatus lardon3d_incremental_reconstruction_run(
|
||||||
|
const Lardon3DIncrementalReconstructionInput *input,
|
||||||
|
Lardon3DIncrementalReconstructionResult *result);
|
||||||
|
void lardon3d_incremental_reconstruction_result_destroy(
|
||||||
|
Lardon3DIncrementalReconstructionResult *result);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
42
include/lardon3d/incremental_reconstruction_task.h
Normal file
42
include/lardon3d/incremental_reconstruction_task.h
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#ifndef LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_H
|
||||||
|
#define LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <lardon3d/task_kind_registry.h>
|
||||||
|
|
||||||
|
typedef struct Lardon3DAppState Lardon3DAppState;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND \
|
||||||
|
"incremental_reconstruction.run"
|
||||||
|
|
||||||
|
enum { LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND_VERSION = 1 };
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t base_reconstruction_id;
|
||||||
|
uint64_t extension_track_set_id;
|
||||||
|
uint64_t calibration_scope_id;
|
||||||
|
} Lardon3DIncrementalReconstructionTaskConfiguration;
|
||||||
|
|
||||||
|
Lardon3DTask *lardon3d_project_create_incremental_reconstruction_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DIncrementalReconstructionTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id);
|
||||||
|
bool lardon3d_project_enqueue_incremental_reconstruction_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DIncrementalReconstructionTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id);
|
||||||
|
bool lardon3d_incremental_reconstruction_task_reconstruct(
|
||||||
|
const Lardon3DTaskDurableSnapshot *snapshot, void *context,
|
||||||
|
Lardon3DTaskKindBinding *binding);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
#include <lardon3d/sparse_sfm_incremental.h>
|
#include <lardon3d/sparse_sfm_incremental.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 17,
|
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 18,
|
||||||
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
||||||
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
||||||
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
||||||
|
|
@ -401,6 +401,16 @@ typedef struct {
|
||||||
Lardon3DSparseIncrementalParameters parameters;
|
Lardon3DSparseIncrementalParameters parameters;
|
||||||
} Lardon3DProjectDbSparseSfmTask;
|
} Lardon3DProjectDbSparseSfmTask;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t task_id;
|
||||||
|
uint64_t base_reconstruction_id;
|
||||||
|
uint64_t extension_track_set_id;
|
||||||
|
uint64_t calibration_scope_id;
|
||||||
|
uint32_t incremental_kind;
|
||||||
|
uint32_t incremental_version;
|
||||||
|
unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
|
} Lardon3DProjectDbIncrementalReconstructionTask;
|
||||||
|
|
||||||
void lardon3d_project_db_free_track(Lardon3DProjectDbTrack *track);
|
void lardon3d_project_db_free_track(Lardon3DProjectDbTrack *track);
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_create_track_set(
|
Lardon3DProjectDbResult lardon3d_project_db_create_track_set(
|
||||||
Lardon3DProjectDb *database, const Lardon3DProjectDbTrackSet *configuration,
|
Lardon3DProjectDb *database, const Lardon3DProjectDbTrackSet *configuration,
|
||||||
|
|
@ -620,6 +630,15 @@ Lardon3DProjectDbResult lardon3d_project_db_record_sparse_sfm_task(
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_sparse_sfm_task(
|
Lardon3DProjectDbResult lardon3d_project_db_load_sparse_sfm_task(
|
||||||
Lardon3DProjectDb *database, uint64_t task_id,
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
Lardon3DProjectDbSparseSfmTask *parameters);
|
Lardon3DProjectDbSparseSfmTask *parameters);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_record_incremental_reconstruction_task(
|
||||||
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
|
const char *kind, uint32_t version,
|
||||||
|
const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||||
|
const Lardon3DProjectDbIncrementalReconstructionTask *parameters,
|
||||||
|
int64_t updated_at);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_incremental_reconstruction_task(
|
||||||
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
|
Lardon3DProjectDbIncrementalReconstructionTask *parameters);
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_create_match_result(
|
Lardon3DProjectDbResult lardon3d_project_db_create_match_result(
|
||||||
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,
|
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ typedef struct {
|
||||||
uint32_t sfm_kind;
|
uint32_t sfm_kind;
|
||||||
uint32_t sfm_version;
|
uint32_t sfm_version;
|
||||||
unsigned char parameter_fingerprint[32];
|
unsigned char parameter_fingerprint[32];
|
||||||
|
bool has_derivation_identity;
|
||||||
|
unsigned char derivation_identity[32];
|
||||||
uint64_t component_count;
|
uint64_t component_count;
|
||||||
uint64_t registered_image_count;
|
uint64_t registered_image_count;
|
||||||
uint64_t landmark_count;
|
uint64_t landmark_count;
|
||||||
|
|
@ -120,6 +122,17 @@ typedef struct {
|
||||||
int64_t created_at;
|
int64_t created_at;
|
||||||
} Lardon3DSparsePublication;
|
} Lardon3DSparsePublication;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t reconstruction_id;
|
||||||
|
uint64_t base_reconstruction_id;
|
||||||
|
uint64_t extension_track_set_id;
|
||||||
|
uint64_t calibration_scope_id;
|
||||||
|
uint32_t incremental_kind;
|
||||||
|
uint32_t incremental_version;
|
||||||
|
unsigned char parameter_fingerprint[32];
|
||||||
|
unsigned char scientific_identity[32];
|
||||||
|
} Lardon3DIncrementalReconstructionMetadata;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint64_t after_id;
|
uint64_t after_id;
|
||||||
size_t capacity;
|
size_t capacity;
|
||||||
|
|
@ -200,6 +213,17 @@ Lardon3DProjectDbResult lardon3d_sparse_calibration_scope_list_members(
|
||||||
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
||||||
Lardon3DProjectDb *database, const Lardon3DSparsePublication *publication,
|
Lardon3DProjectDb *database, const Lardon3DSparsePublication *publication,
|
||||||
Lardon3DSparseReconstruction *output);
|
Lardon3DSparseReconstruction *output);
|
||||||
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_publish(
|
||||||
|
Lardon3DProjectDb *database, const Lardon3DSparsePublication *publication,
|
||||||
|
const Lardon3DIncrementalReconstructionMetadata *metadata,
|
||||||
|
Lardon3DSparseReconstruction *output);
|
||||||
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
Lardon3DProjectDb *database, const unsigned char scientific_identity[32],
|
||||||
|
Lardon3DIncrementalReconstructionMetadata *metadata,
|
||||||
|
Lardon3DSparseReconstruction *reconstruction);
|
||||||
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_load_metadata(
|
||||||
|
Lardon3DProjectDb *database, uint64_t reconstruction_id,
|
||||||
|
Lardon3DIncrementalReconstructionMetadata *metadata);
|
||||||
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_find_exact(
|
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_find_exact(
|
||||||
Lardon3DProjectDb *database, uint64_t track_set_id,
|
Lardon3DProjectDb *database, uint64_t track_set_id,
|
||||||
uint64_t calibration_scope_id, uint32_t sfm_kind, uint32_t sfm_version,
|
uint64_t calibration_scope_id, uint32_t sfm_kind, uint32_t sfm_version,
|
||||||
|
|
|
||||||
22
meson.build
22
meson.build
|
|
@ -155,6 +155,9 @@ executable(
|
||||||
'src/sparse_sfm_bundle_adjustment.cpp',
|
'src/sparse_sfm_bundle_adjustment.cpp',
|
||||||
'src/sparse_sfm_gate_f.cpp',
|
'src/sparse_sfm_gate_f.cpp',
|
||||||
'src/sparse_sfm_task.cpp',
|
'src/sparse_sfm_task.cpp',
|
||||||
|
'src/incremental_reconstruction_identity.cpp',
|
||||||
|
'src/incremental_reconstruction.cpp',
|
||||||
|
'src/incremental_reconstruction_task.cpp',
|
||||||
'src/task.c',
|
'src/task.c',
|
||||||
'src/task_checkpoint.c',
|
'src/task_checkpoint.c',
|
||||||
'src/task_kind_registry.c',
|
'src/task_kind_registry.c',
|
||||||
|
|
@ -174,6 +177,7 @@ executable(
|
||||||
c_args: [
|
c_args: [
|
||||||
'-DLARDON3D_TRACK_BUILDER_TASK_AVAILABLE',
|
'-DLARDON3D_TRACK_BUILDER_TASK_AVAILABLE',
|
||||||
'-DLARDON3D_SPARSE_SFM_TASK_AVAILABLE',
|
'-DLARDON3D_SPARSE_SFM_TASK_AVAILABLE',
|
||||||
|
'-DLARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_AVAILABLE',
|
||||||
],
|
],
|
||||||
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry, ceres]
|
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry, ceres]
|
||||||
+ matcher_backend_dependencies,
|
+ matcher_backend_dependencies,
|
||||||
|
|
@ -945,6 +949,9 @@ sparse_sfm_task_test = executable(
|
||||||
'src/sparse_sfm_incremental.cpp',
|
'src/sparse_sfm_incremental.cpp',
|
||||||
'src/sparse_sfm_geometry.cpp',
|
'src/sparse_sfm_geometry.cpp',
|
||||||
'src/sparse_sfm_bundle_adjustment.cpp',
|
'src/sparse_sfm_bundle_adjustment.cpp',
|
||||||
|
'src/incremental_reconstruction_identity.cpp',
|
||||||
|
'src/incremental_reconstruction.cpp',
|
||||||
|
'src/incremental_reconstruction_task.cpp',
|
||||||
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||||
'src/feature_store.c', 'src/feature_extractor_opencv.cpp',
|
'src/feature_store.c', 'src/feature_extractor_opencv.cpp',
|
||||||
'src/app_state.c',
|
'src/app_state.c',
|
||||||
|
|
@ -958,6 +965,21 @@ sparse_sfm_task_test = executable(
|
||||||
)
|
)
|
||||||
test('sparse-sfm-task', sparse_sfm_task_test, timeout: 120)
|
test('sparse-sfm-task', sparse_sfm_task_test, timeout: 120)
|
||||||
|
|
||||||
|
incremental_reconstruction_test = executable(
|
||||||
|
'test-incremental-reconstruction',
|
||||||
|
sources: [
|
||||||
|
'tests/test_incremental_reconstruction.cpp',
|
||||||
|
'src/incremental_reconstruction_identity.cpp',
|
||||||
|
'src/incremental_reconstruction.cpp',
|
||||||
|
'src/sparse_sfm_geometry.cpp',
|
||||||
|
'src/sparse_sfm_incremental.cpp',
|
||||||
|
'src/sparse_sfm_bundle_adjustment.cpp',
|
||||||
|
],
|
||||||
|
include_directories: include_directories('include'),
|
||||||
|
dependencies: [openssl, opencv_geometry, ceres],
|
||||||
|
)
|
||||||
|
test('incremental-reconstruction', incremental_reconstruction_test, timeout: 120)
|
||||||
|
|
||||||
sparse_sfm_payload_test = executable(
|
sparse_sfm_payload_test = executable(
|
||||||
'test-sparse-sfm-payload',
|
'test-sparse-sfm-payload',
|
||||||
sources: [
|
sources: [
|
||||||
|
|
|
||||||
786
src/incremental_reconstruction.cpp
Normal file
786
src/incremental_reconstruction.cpp
Normal file
|
|
@ -0,0 +1,786 @@
|
||||||
|
#include "incremental_reconstruction_internal.h"
|
||||||
|
|
||||||
|
#include <lardon3d/incremental_reconstruction.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <map>
|
||||||
|
#include <new>
|
||||||
|
#include <set>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct ObservationKey {
|
||||||
|
uint64_t feature_set_id;
|
||||||
|
uint32_t feature_index;
|
||||||
|
|
||||||
|
bool operator<(const ObservationKey &other) const {
|
||||||
|
return std::tie(feature_set_id, feature_index) <
|
||||||
|
std::tie(other.feature_set_id, other.feature_index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HistoricalObservationKey {
|
||||||
|
uint64_t track_id;
|
||||||
|
uint64_t image_id;
|
||||||
|
uint64_t feature_set_id;
|
||||||
|
uint32_t feature_index;
|
||||||
|
|
||||||
|
bool operator<(const HistoricalObservationKey &other) const {
|
||||||
|
return std::tie(track_id, image_id, feature_set_id, feature_index) <
|
||||||
|
std::tie(other.track_id, other.image_id, other.feature_set_id,
|
||||||
|
other.feature_index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Lineage {
|
||||||
|
uint64_t base_track_id;
|
||||||
|
uint64_t extension_track_id;
|
||||||
|
uint64_t component_key;
|
||||||
|
uint64_t landmark_id;
|
||||||
|
std::set<ObservationKey> historical;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool finite_calibration(const Lardon3DSparseGeometryCalibration &value) {
|
||||||
|
return value.width > 0 && value.height > 0 && std::isfinite(value.fx) &&
|
||||||
|
std::isfinite(value.fy) && std::isfinite(value.cx) &&
|
||||||
|
std::isfinite(value.cy) && std::isfinite(value.k1) &&
|
||||||
|
std::isfinite(value.k2) && std::isfinite(value.p1) &&
|
||||||
|
std::isfinite(value.p2) && value.fx > 0.0 && value.fy > 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool finite_pose(const Lardon3DSparseGeometryPose &value) {
|
||||||
|
for (double item : value.rotation_cw)
|
||||||
|
if (!std::isfinite(item)) return false;
|
||||||
|
for (double item : value.translation_cw)
|
||||||
|
if (!std::isfinite(item)) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool finite_point(const Lardon3DSparseGeometryPoint3 &value) {
|
||||||
|
return std::isfinite(value.x) && std::isfinite(value.y) &&
|
||||||
|
std::isfinite(value.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkpoint(const Lardon3DIncrementalReconstructionInput &input) {
|
||||||
|
return !input.checkpoint || input.checkpoint(input.checkpoint_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
bool copy_array(const std::vector<T> &source, T **output) {
|
||||||
|
*output = nullptr;
|
||||||
|
if (source.empty()) return true;
|
||||||
|
if (source.size() > SIZE_MAX / sizeof(T)) return false;
|
||||||
|
*output = static_cast<T *>(std::malloc(source.size() * sizeof(T)));
|
||||||
|
if (!*output) return false;
|
||||||
|
std::copy(source.begin(), source.end(), *output);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_snapshot(Lardon3DSparseIncrementalResult *snapshot) {
|
||||||
|
if (!snapshot) return;
|
||||||
|
std::free(snapshot->components);
|
||||||
|
std::free(snapshot->cameras);
|
||||||
|
std::free(snapshot->landmarks);
|
||||||
|
std::free(snapshot->observations);
|
||||||
|
std::free(snapshot->unregistered_images);
|
||||||
|
*snapshot = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valid_base(const Lardon3DSparseIncrementalResult &base) {
|
||||||
|
if ((base.status != LARDON3D_SPARSE_INCREMENTAL_COMPLETE &&
|
||||||
|
base.status != LARDON3D_SPARSE_INCREMENTAL_PARTIAL) ||
|
||||||
|
base.component_count == 0 || base.camera_count < 2 ||
|
||||||
|
base.landmark_count == 0 || base.observation_count == 0 ||
|
||||||
|
!base.components || !base.cameras || !base.landmarks || !base.observations)
|
||||||
|
return false;
|
||||||
|
std::map<uint64_t, uint64_t> component_min_image;
|
||||||
|
std::map<uint64_t, uint64_t> component_camera_count;
|
||||||
|
std::map<uint64_t, uint64_t> component_landmark_count;
|
||||||
|
std::map<uint64_t, uint64_t> camera_components;
|
||||||
|
std::map<uint64_t, const Lardon3DSparseIncrementalLandmark *> landmarks_by_track;
|
||||||
|
std::set<uint64_t> landmark_ids;
|
||||||
|
for (size_t index = 0; index < base.component_count; ++index) {
|
||||||
|
const auto &component = base.components[index];
|
||||||
|
if (component.component_key == 0 || component.registered_image_count == 0 ||
|
||||||
|
component.landmark_count == 0 ||
|
||||||
|
(index && base.components[index - 1].component_key >= component.component_key))
|
||||||
|
return false;
|
||||||
|
component_min_image.emplace(component.component_key, UINT64_MAX);
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < base.camera_count; ++index) {
|
||||||
|
const auto &camera = base.cameras[index];
|
||||||
|
auto minimum = component_min_image.find(camera.component_key);
|
||||||
|
if (camera.image_id == 0 || minimum == component_min_image.end() ||
|
||||||
|
!finite_pose(camera.pose_cw) ||
|
||||||
|
!camera_components.emplace(camera.image_id, camera.component_key).second)
|
||||||
|
return false;
|
||||||
|
minimum->second = std::min(minimum->second, camera.image_id);
|
||||||
|
++component_camera_count[camera.component_key];
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < base.landmark_count; ++index) {
|
||||||
|
const auto &landmark = base.landmarks[index];
|
||||||
|
if (landmark.landmark_id == 0 || landmark.track_id == 0 ||
|
||||||
|
component_min_image.find(landmark.component_key) == component_min_image.end() ||
|
||||||
|
!finite_point(landmark.point) || landmark.observation_count < 2 ||
|
||||||
|
!landmark_ids.insert(landmark.landmark_id).second ||
|
||||||
|
!landmarks_by_track.emplace(landmark.track_id, &landmark).second)
|
||||||
|
return false;
|
||||||
|
++component_landmark_count[landmark.component_key];
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < base.component_count; ++index) {
|
||||||
|
const auto &component = base.components[index];
|
||||||
|
if (component_min_image[component.component_key] != component.component_key ||
|
||||||
|
component_camera_count[component.component_key] !=
|
||||||
|
component.registered_image_count ||
|
||||||
|
component_landmark_count[component.component_key] != component.landmark_count)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::map<uint64_t, uint64_t> observation_counts;
|
||||||
|
std::map<uint64_t, std::set<uint32_t>> positions;
|
||||||
|
std::set<ObservationKey> keys;
|
||||||
|
for (size_t index = 0; index < base.observation_count; ++index) {
|
||||||
|
const auto &observation = base.observations[index];
|
||||||
|
auto landmark = landmarks_by_track.find(observation.track_id);
|
||||||
|
auto camera = camera_components.find(observation.image_id);
|
||||||
|
if (landmark == landmarks_by_track.end() ||
|
||||||
|
observation.landmark_id != landmark->second->landmark_id ||
|
||||||
|
camera == camera_components.end() ||
|
||||||
|
camera->second != landmark->second->component_key ||
|
||||||
|
observation.feature_set_id == 0 ||
|
||||||
|
observation.position_in_track >= landmark->second->observation_count ||
|
||||||
|
!positions[observation.track_id]
|
||||||
|
.insert(observation.position_in_track)
|
||||||
|
.second ||
|
||||||
|
!keys.insert({observation.feature_set_id, observation.feature_index}).second)
|
||||||
|
return false;
|
||||||
|
++observation_counts[observation.track_id];
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < base.landmark_count; ++index)
|
||||||
|
if (observation_counts[base.landmarks[index].track_id] !=
|
||||||
|
base.landmarks[index].observation_count ||
|
||||||
|
positions[base.landmarks[index].track_id].size() !=
|
||||||
|
base.landmarks[index].observation_count)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DIncrementalReconstructionStatus build_lineage(
|
||||||
|
const Lardon3DIncrementalReconstructionInput &input,
|
||||||
|
std::vector<Lineage> *lineages,
|
||||||
|
std::map<uint64_t, size_t> *extension_lineage,
|
||||||
|
std::map<uint64_t,
|
||||||
|
std::vector<const Lardon3DSparseIncrementalObservation *>>
|
||||||
|
*extension_tracks,
|
||||||
|
std::map<ObservationKey, const Lardon3DSparseIncrementalObservation *> *extension_keys) {
|
||||||
|
const auto &base = *input.base;
|
||||||
|
std::map<uint64_t, const Lardon3DSparseIncrementalLandmark *> base_landmarks;
|
||||||
|
for (size_t index = 0; index < base.landmark_count; ++index)
|
||||||
|
base_landmarks.emplace(base.landmarks[index].track_id, &base.landmarks[index]);
|
||||||
|
|
||||||
|
std::map<uint64_t, std::vector<const Lardon3DSparseIncrementalObservation *>> base_tracks;
|
||||||
|
std::map<HistoricalObservationKey,
|
||||||
|
const Lardon3DSparseIncrementalLandmarkObservation *>
|
||||||
|
snapshot_historical;
|
||||||
|
std::map<HistoricalObservationKey,
|
||||||
|
const Lardon3DSparseIncrementalObservation *>
|
||||||
|
supplied_historical;
|
||||||
|
for (size_t index = 0; index < base.observation_count; ++index) {
|
||||||
|
const auto &observation = base.observations[index];
|
||||||
|
const HistoricalObservationKey key{observation.track_id, observation.image_id,
|
||||||
|
observation.feature_set_id,
|
||||||
|
observation.feature_index};
|
||||||
|
if (!snapshot_historical.emplace(key, &observation).second)
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
}
|
||||||
|
std::set<ObservationKey> historical_keys;
|
||||||
|
for (size_t index = 0; index < input.base_track_observation_count; ++index) {
|
||||||
|
const auto &observation = input.base_track_observations[index];
|
||||||
|
const HistoricalObservationKey key{observation.track_id, observation.image_id,
|
||||||
|
observation.feature_set_id,
|
||||||
|
observation.feature_index};
|
||||||
|
if (!base_landmarks.count(observation.track_id) || observation.image_id == 0 ||
|
||||||
|
observation.feature_set_id == 0 || observation.feature_count == 0 ||
|
||||||
|
observation.feature_index >= observation.feature_count ||
|
||||||
|
!std::isfinite(observation.x) || !std::isfinite(observation.y) ||
|
||||||
|
!supplied_historical.emplace(key, &observation).second ||
|
||||||
|
!historical_keys.insert({observation.feature_set_id,
|
||||||
|
observation.feature_index}).second)
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
base_tracks[observation.track_id].push_back(&observation);
|
||||||
|
}
|
||||||
|
if (base_tracks.size() != base.landmark_count ||
|
||||||
|
supplied_historical.size() != snapshot_historical.size())
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
for (const auto &[key, observation] : snapshot_historical) {
|
||||||
|
(void)observation;
|
||||||
|
if (!supplied_historical.count(key))
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t index = 0; index < input.extension->observation_count; ++index) {
|
||||||
|
const auto &observation = input.extension->observations[index];
|
||||||
|
if (observation.track_id == 0 || observation.image_id == 0 ||
|
||||||
|
observation.feature_set_id == 0 || observation.feature_count == 0 ||
|
||||||
|
observation.feature_index >= observation.feature_count ||
|
||||||
|
!std::isfinite(observation.x) || !std::isfinite(observation.y) ||
|
||||||
|
!extension_keys->emplace(ObservationKey{observation.feature_set_id,
|
||||||
|
observation.feature_index},
|
||||||
|
&observation).second)
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_DUPLICATE;
|
||||||
|
(*extension_tracks)[observation.track_id].push_back(&observation);
|
||||||
|
}
|
||||||
|
|
||||||
|
lineages->reserve(base.landmark_count);
|
||||||
|
for (const auto &[base_track_id, historical] : base_tracks) {
|
||||||
|
uint64_t descendant = 0;
|
||||||
|
std::set<ObservationKey> base_keys;
|
||||||
|
for (const auto *observation : historical) {
|
||||||
|
ObservationKey key{observation->feature_set_id, observation->feature_index};
|
||||||
|
base_keys.insert(key);
|
||||||
|
auto found = extension_keys->find(key);
|
||||||
|
if (found == extension_keys->end())
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MISSING;
|
||||||
|
if (descendant == 0)
|
||||||
|
descendant = found->second->track_id;
|
||||||
|
else if (descendant != found->second->track_id)
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_SPLIT;
|
||||||
|
}
|
||||||
|
if (descendant == 0)
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MISSING;
|
||||||
|
const auto *landmark = base_landmarks.at(base_track_id);
|
||||||
|
Lineage lineage{base_track_id, descendant, landmark->component_key,
|
||||||
|
landmark->landmark_id, std::move(base_keys)};
|
||||||
|
auto inserted = extension_lineage->emplace(descendant, lineages->size());
|
||||||
|
if (!inserted.second) {
|
||||||
|
const auto &other = (*lineages)[inserted.first->second];
|
||||||
|
return other.component_key == lineage.component_key
|
||||||
|
? LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MERGE
|
||||||
|
: LARDON3D_INCREMENTAL_RECONSTRUCTION_CROSS_COMPONENT_BRIDGE;
|
||||||
|
}
|
||||||
|
lineages->push_back(std::move(lineage));
|
||||||
|
}
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Candidate {
|
||||||
|
uint64_t image_id;
|
||||||
|
uint64_t component_key;
|
||||||
|
size_t support;
|
||||||
|
std::vector<Lardon3DSparseGeometryPoint3> points;
|
||||||
|
std::vector<Lardon3DSparseGeometryPoint2> pixels;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool register_cameras(
|
||||||
|
const Lardon3DIncrementalReconstructionInput &input,
|
||||||
|
const std::vector<Lineage> &lineages,
|
||||||
|
const std::map<uint64_t, size_t> &extension_lineage,
|
||||||
|
std::vector<Lardon3DSparseIncrementalCamera> *cameras,
|
||||||
|
std::set<uint64_t> *affected,
|
||||||
|
Lardon3DIncrementalReconstructionStatus *failure) {
|
||||||
|
std::map<uint64_t, const Lardon3DSparseIncrementalLandmark *> landmarks;
|
||||||
|
for (size_t index = 0; index < input.base->landmark_count; ++index) {
|
||||||
|
const auto &landmark = input.base->landmarks[index];
|
||||||
|
landmarks.emplace(landmark.track_id, &landmark);
|
||||||
|
}
|
||||||
|
std::set<uint64_t> registered;
|
||||||
|
std::map<uint64_t, uint64_t> component_keys;
|
||||||
|
for (const auto &camera : *cameras) {
|
||||||
|
registered.insert(camera.image_id);
|
||||||
|
component_keys.emplace(camera.component_key, camera.component_key);
|
||||||
|
}
|
||||||
|
std::set<uint64_t> remaining;
|
||||||
|
std::map<uint64_t, const Lardon3DSparseIncrementalImage *> images_by_id;
|
||||||
|
std::map<uint64_t, std::vector<const Lardon3DSparseIncrementalObservation *>>
|
||||||
|
observations_by_image;
|
||||||
|
for (size_t index = 0; index < input.extension->observation_count; ++index) {
|
||||||
|
const auto &observation = input.extension->observations[index];
|
||||||
|
observations_by_image[observation.image_id].push_back(&observation);
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < input.extension->image_count; ++index) {
|
||||||
|
const auto &image = input.extension->images[index];
|
||||||
|
if (image.image_id == 0 || !finite_calibration(image.calibration) ||
|
||||||
|
!images_by_id.emplace(image.image_id, &image).second)
|
||||||
|
return false;
|
||||||
|
if (!registered.count(image.image_id)) remaining.insert(image.image_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint32_t round = 0;
|
||||||
|
round < input.parameters->maximum_registration_rounds && !remaining.empty();
|
||||||
|
++round) {
|
||||||
|
std::vector<Candidate> candidates;
|
||||||
|
for (uint64_t image_id : remaining) {
|
||||||
|
std::map<uint64_t, Candidate> by_component;
|
||||||
|
const auto image_observations = observations_by_image.find(image_id);
|
||||||
|
if (image_observations == observations_by_image.end()) continue;
|
||||||
|
for (const auto *observation_pointer : image_observations->second) {
|
||||||
|
const auto &observation = *observation_pointer;
|
||||||
|
auto lineage_index = extension_lineage.find(observation.track_id);
|
||||||
|
if (lineage_index == extension_lineage.end()) continue;
|
||||||
|
const auto &lineage = lineages[lineage_index->second];
|
||||||
|
const auto *landmark = landmarks.at(lineage.base_track_id);
|
||||||
|
auto &candidate = by_component[lineage.component_key];
|
||||||
|
candidate.image_id = image_id;
|
||||||
|
candidate.component_key = lineage.component_key;
|
||||||
|
candidate.points.push_back(landmark->point);
|
||||||
|
candidate.pixels.push_back({observation.x, observation.y});
|
||||||
|
}
|
||||||
|
size_t eligible_components = 0;
|
||||||
|
for (auto &[component, candidate] : by_component) {
|
||||||
|
candidate.support = candidate.points.size();
|
||||||
|
if (candidate.support >= input.parameters->minimum_pnp_correspondences) {
|
||||||
|
++eligible_components;
|
||||||
|
candidates.push_back(std::move(candidate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eligible_components > 1) {
|
||||||
|
*failure = LARDON3D_INCREMENTAL_RECONSTRUCTION_CROSS_COMPONENT_BRIDGE;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::sort(candidates.begin(), candidates.end(), [](const auto &left, const auto &right) {
|
||||||
|
return left.support != right.support ? left.support > right.support
|
||||||
|
: left.image_id < right.image_id;
|
||||||
|
});
|
||||||
|
bool grew = false;
|
||||||
|
for (const auto &candidate : candidates) {
|
||||||
|
if (!remaining.count(candidate.image_id)) continue;
|
||||||
|
auto image_entry = images_by_id.find(candidate.image_id);
|
||||||
|
const auto *image = image_entry == images_by_id.end()
|
||||||
|
? nullptr
|
||||||
|
: image_entry->second;
|
||||||
|
std::vector<uint8_t> mask(candidate.support);
|
||||||
|
Lardon3DSparseGeometryPnPResult pnp{};
|
||||||
|
pnp.inlier_mask = mask.data();
|
||||||
|
pnp.inlier_mask_capacity = mask.size();
|
||||||
|
if (!image || lardon3d_sparse_geometry_pnp(
|
||||||
|
&image->calibration, candidate.points.data(),
|
||||||
|
candidate.pixels.data(), candidate.support,
|
||||||
|
&input.parameters->pnp, &pnp) != LARDON3D_SPARSE_GEOMETRY_OK ||
|
||||||
|
!finite_pose(pnp.pose_cw))
|
||||||
|
continue;
|
||||||
|
if (candidate.image_id < candidate.component_key) {
|
||||||
|
*failure = LARDON3D_INCREMENTAL_RECONSTRUCTION_COMPONENT_KEY_VIOLATION;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
cameras->push_back({candidate.image_id, candidate.component_key, pnp.pose_cw});
|
||||||
|
remaining.erase(candidate.image_id);
|
||||||
|
affected->insert(candidate.component_key);
|
||||||
|
grew = true;
|
||||||
|
}
|
||||||
|
if (!grew) break;
|
||||||
|
}
|
||||||
|
std::sort(cameras->begin(), cameras->end(),
|
||||||
|
[](const auto &left, const auto &right) {
|
||||||
|
return left.image_id < right.image_id;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool project_ok(const Lardon3DSparseGeometryCalibration &calibration,
|
||||||
|
const Lardon3DSparseGeometryPose &pose,
|
||||||
|
const Lardon3DSparseGeometryPoint3 &point,
|
||||||
|
const Lardon3DSparseGeometryPoint2 &observed, double threshold) {
|
||||||
|
const double *r = pose.rotation_cw;
|
||||||
|
const double *t = pose.translation_cw;
|
||||||
|
const double x = r[0] * point.x + r[1] * point.y + r[2] * point.z + t[0];
|
||||||
|
const double y = r[3] * point.x + r[4] * point.y + r[5] * point.z + t[1];
|
||||||
|
const double z = r[6] * point.x + r[7] * point.y + r[8] * point.z + t[2];
|
||||||
|
if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) || z <= 1e-9)
|
||||||
|
return false;
|
||||||
|
const double xn = x / z;
|
||||||
|
const double yn = y / z;
|
||||||
|
const double r2 = xn * xn + yn * yn;
|
||||||
|
const double radial = 1.0 + calibration.k1 * r2 + calibration.k2 * r2 * r2;
|
||||||
|
const double px = calibration.fx *
|
||||||
|
(xn * radial + 2.0 * calibration.p1 * xn * yn +
|
||||||
|
calibration.p2 * (r2 + 2.0 * xn * xn)) +
|
||||||
|
calibration.cx;
|
||||||
|
const double py = calibration.fy *
|
||||||
|
(yn * radial + calibration.p1 * (r2 + 2.0 * yn * yn) +
|
||||||
|
2.0 * calibration.p2 * xn * yn) +
|
||||||
|
calibration.cy;
|
||||||
|
return std::isfinite(px) && std::isfinite(py) &&
|
||||||
|
std::hypot(px - observed.x, py - observed.y) <= threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool select_base_anchor(const Lardon3DSparseIncrementalResult &base,
|
||||||
|
uint64_t component_key,
|
||||||
|
Lardon3DSparseBundleAdjustmentAnchor *anchor) {
|
||||||
|
std::vector<const Lardon3DSparseIncrementalCamera *> cameras;
|
||||||
|
for (size_t index = 0; index < base.camera_count; ++index)
|
||||||
|
if (base.cameras[index].component_key == component_key)
|
||||||
|
cameras.push_back(&base.cameras[index]);
|
||||||
|
if (cameras.size() < 2) return false;
|
||||||
|
std::sort(cameras.begin(), cameras.end(), [](const auto *left, const auto *right) {
|
||||||
|
return left->image_id < right->image_id;
|
||||||
|
});
|
||||||
|
const auto *pose = cameras.front();
|
||||||
|
auto center = [](const auto &camera, double output[3]) {
|
||||||
|
const double *r = camera.pose_cw.rotation_cw;
|
||||||
|
const double *t = camera.pose_cw.translation_cw;
|
||||||
|
for (size_t column = 0; column < 3; ++column)
|
||||||
|
output[column] = -(r[column] * t[0] + r[3 + column] * t[1] +
|
||||||
|
r[6 + column] * t[2]);
|
||||||
|
};
|
||||||
|
double pose_center[3];
|
||||||
|
center(*pose, pose_center);
|
||||||
|
const Lardon3DSparseIncrementalCamera *scale = nullptr;
|
||||||
|
double best = -1.0;
|
||||||
|
double best_delta[3]{};
|
||||||
|
for (size_t index = 1; index < cameras.size(); ++index) {
|
||||||
|
double candidate_center[3];
|
||||||
|
center(*cameras[index], candidate_center);
|
||||||
|
double delta[3] = {candidate_center[0] - pose_center[0],
|
||||||
|
candidate_center[1] - pose_center[1],
|
||||||
|
candidate_center[2] - pose_center[2]};
|
||||||
|
const double distance = std::hypot(delta[0], delta[1], delta[2]);
|
||||||
|
if (!scale || distance > best ||
|
||||||
|
(distance == best && cameras[index]->image_id < scale->image_id)) {
|
||||||
|
scale = cameras[index];
|
||||||
|
best = distance;
|
||||||
|
std::copy(delta, delta + 3, best_delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size_t axis = 0;
|
||||||
|
if (std::abs(best_delta[1]) > std::abs(best_delta[axis])) axis = 1;
|
||||||
|
if (std::abs(best_delta[2]) > std::abs(best_delta[axis])) axis = 2;
|
||||||
|
if (!scale || std::abs(best_delta[axis]) <= 1e-9) return false;
|
||||||
|
*anchor = {component_key, pose->image_id, scale->image_id,
|
||||||
|
static_cast<Lardon3DSparseBundleAdjustmentScaleAxis>(axis + 1)};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool full_ba_affected(
|
||||||
|
const Lardon3DIncrementalReconstructionInput &input,
|
||||||
|
const std::set<uint64_t> &affected,
|
||||||
|
std::vector<Lardon3DSparseIncrementalComponent> *components,
|
||||||
|
std::vector<Lardon3DSparseIncrementalCamera> *cameras,
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmark> *landmarks,
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmarkObservation> *observations) {
|
||||||
|
std::map<uint64_t, size_t> component_indexes;
|
||||||
|
std::map<uint64_t, size_t> camera_indexes;
|
||||||
|
std::map<uint64_t, size_t> landmark_indexes;
|
||||||
|
std::map<uint64_t, std::vector<size_t>> cameras_by_component;
|
||||||
|
std::map<uint64_t, std::vector<size_t>> landmarks_by_component;
|
||||||
|
std::map<uint64_t, std::vector<size_t>> observations_by_component;
|
||||||
|
std::map<uint64_t, uint64_t> track_components;
|
||||||
|
for (size_t index = 0; index < components->size(); ++index)
|
||||||
|
component_indexes.emplace((*components)[index].component_key, index);
|
||||||
|
for (size_t index = 0; index < cameras->size(); ++index) {
|
||||||
|
camera_indexes.emplace((*cameras)[index].image_id, index);
|
||||||
|
cameras_by_component[(*cameras)[index].component_key].push_back(index);
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < landmarks->size(); ++index) {
|
||||||
|
landmark_indexes.emplace((*landmarks)[index].track_id, index);
|
||||||
|
landmarks_by_component[(*landmarks)[index].component_key].push_back(index);
|
||||||
|
track_components.emplace((*landmarks)[index].track_id,
|
||||||
|
(*landmarks)[index].component_key);
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < observations->size(); ++index) {
|
||||||
|
auto component = track_components.find((*observations)[index].track_id);
|
||||||
|
if (component == track_components.end()) return false;
|
||||||
|
observations_by_component[component->second].push_back(index);
|
||||||
|
}
|
||||||
|
for (uint64_t key : affected) {
|
||||||
|
std::vector<Lardon3DSparseIncrementalComponent> sub_components;
|
||||||
|
std::vector<Lardon3DSparseIncrementalCamera> sub_cameras;
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmark> sub_landmarks;
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmarkObservation> sub_observations;
|
||||||
|
auto component = component_indexes.find(key);
|
||||||
|
if (component == component_indexes.end()) return false;
|
||||||
|
sub_components.push_back((*components)[component->second]);
|
||||||
|
for (size_t index : cameras_by_component[key])
|
||||||
|
sub_cameras.push_back((*cameras)[index]);
|
||||||
|
for (size_t index : landmarks_by_component[key])
|
||||||
|
sub_landmarks.push_back((*landmarks)[index]);
|
||||||
|
for (size_t index : observations_by_component[key])
|
||||||
|
sub_observations.push_back((*observations)[index]);
|
||||||
|
Lardon3DSparseIncrementalResult sub{};
|
||||||
|
sub.status = LARDON3D_SPARSE_INCREMENTAL_COMPLETE;
|
||||||
|
sub.components = sub_components.data();
|
||||||
|
sub.component_count = sub_components.size();
|
||||||
|
sub.cameras = sub_cameras.data();
|
||||||
|
sub.camera_count = sub_cameras.size();
|
||||||
|
sub.landmarks = sub_landmarks.data();
|
||||||
|
sub.landmark_count = sub_landmarks.size();
|
||||||
|
sub.observations = sub_observations.data();
|
||||||
|
sub.observation_count = sub_observations.size();
|
||||||
|
Lardon3DSparseBundleAdjustmentInput ba_input{
|
||||||
|
&sub, input.extension->images, input.extension->image_count,
|
||||||
|
input.extension->observations, input.extension->observation_count};
|
||||||
|
Lardon3DSparseBundleAdjustmentAnchor anchor{};
|
||||||
|
if (!select_base_anchor(*input.base, key, &anchor)) return false;
|
||||||
|
Lardon3DSparseBundleAdjustmentResult adjusted{};
|
||||||
|
auto status = lardon3d_sparse_bundle_adjustment_run_with_anchors(
|
||||||
|
&ba_input, &anchor, 1, &adjusted);
|
||||||
|
if (status != LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_OK ||
|
||||||
|
adjusted.status != LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_COMPLETE ||
|
||||||
|
adjusted.component_count != 1 || !adjusted.diagnostics[0].accepted) {
|
||||||
|
lardon3d_sparse_bundle_adjustment_result_destroy(&adjusted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < adjusted.camera_count; ++index) {
|
||||||
|
auto target = camera_indexes.find(adjusted.cameras[index].image_id);
|
||||||
|
if (target == camera_indexes.end()) {
|
||||||
|
lardon3d_sparse_bundle_adjustment_result_destroy(&adjusted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
(*cameras)[target->second] = adjusted.cameras[index];
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < adjusted.landmark_count; ++index) {
|
||||||
|
auto target = landmark_indexes.find(adjusted.landmarks[index].track_id);
|
||||||
|
if (target == landmark_indexes.end()) {
|
||||||
|
lardon3d_sparse_bundle_adjustment_result_destroy(&adjusted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
(*landmarks)[target->second] = adjusted.landmarks[index];
|
||||||
|
}
|
||||||
|
lardon3d_sparse_bundle_adjustment_result_destroy(&adjusted);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DIncrementalReconstructionStatus execute(
|
||||||
|
const Lardon3DIncrementalReconstructionInput &input,
|
||||||
|
Lardon3DIncrementalReconstructionResult *result) {
|
||||||
|
if (!input.base || !input.extension || !input.parameters ||
|
||||||
|
input.base_reconstruction_id == 0 ||
|
||||||
|
(input.base_track_observation_count && !input.base_track_observations) ||
|
||||||
|
(input.extension->image_count && !input.extension->images) ||
|
||||||
|
(input.extension->observation_count && !input.extension->observations))
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_ARGUMENT;
|
||||||
|
if (!valid_base(*input.base))
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
if (!checkpoint(input)) return LARDON3D_INCREMENTAL_RECONSTRUCTION_CANCELLED;
|
||||||
|
|
||||||
|
std::vector<Lineage> lineages;
|
||||||
|
std::map<uint64_t, size_t> extension_lineage;
|
||||||
|
std::map<uint64_t,
|
||||||
|
std::vector<const Lardon3DSparseIncrementalObservation *>>
|
||||||
|
extension_tracks;
|
||||||
|
std::map<ObservationKey, const Lardon3DSparseIncrementalObservation *> extension_keys;
|
||||||
|
auto lineage_status = build_lineage(input, &lineages, &extension_lineage,
|
||||||
|
&extension_tracks, &extension_keys);
|
||||||
|
if (lineage_status != LARDON3D_INCREMENTAL_RECONSTRUCTION_OK) return lineage_status;
|
||||||
|
|
||||||
|
std::vector<Lardon3DSparseIncrementalComponent> components(
|
||||||
|
input.base->components, input.base->components + input.base->component_count);
|
||||||
|
std::vector<Lardon3DSparseIncrementalCamera> cameras(
|
||||||
|
input.base->cameras, input.base->cameras + input.base->camera_count);
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmark> landmarks(
|
||||||
|
input.base->landmarks, input.base->landmarks + input.base->landmark_count);
|
||||||
|
std::set<uint64_t> affected;
|
||||||
|
Lardon3DIncrementalReconstructionStatus registration_failure =
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_GEOMETRY_FAILED;
|
||||||
|
if (!register_cameras(input, lineages, extension_lineage, &cameras, &affected,
|
||||||
|
®istration_failure))
|
||||||
|
return registration_failure;
|
||||||
|
if (!checkpoint(input)) return LARDON3D_INCREMENTAL_RECONSTRUCTION_CANCELLED;
|
||||||
|
|
||||||
|
std::map<uint64_t, uint64_t> registered_component;
|
||||||
|
for (const auto &camera : cameras)
|
||||||
|
registered_component.emplace(camera.image_id, camera.component_key);
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmarkObservation> observations;
|
||||||
|
observations.reserve(input.extension->observation_count);
|
||||||
|
std::map<uint64_t, size_t> landmark_by_track;
|
||||||
|
std::map<uint64_t, size_t> lineage_by_base_track;
|
||||||
|
for (size_t index = 0; index < lineages.size(); ++index)
|
||||||
|
lineage_by_base_track.emplace(lineages[index].base_track_id, index);
|
||||||
|
for (size_t index = 0; index < landmarks.size(); ++index) {
|
||||||
|
auto lineage = lineage_by_base_track.find(landmarks[index].track_id);
|
||||||
|
if (lineage == lineage_by_base_track.end())
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
const auto &lineage_record = lineages[lineage->second];
|
||||||
|
landmarks[index].track_id = lineage_record.extension_track_id;
|
||||||
|
landmarks[index].observation_count = 0;
|
||||||
|
landmark_by_track.emplace(lineage_record.extension_track_id, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &lineage : lineages) {
|
||||||
|
uint32_t position = 0;
|
||||||
|
bool added = false;
|
||||||
|
auto descendant = extension_tracks.find(lineage.extension_track_id);
|
||||||
|
if (descendant == extension_tracks.end())
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MISSING;
|
||||||
|
for (const auto *source_pointer : descendant->second) {
|
||||||
|
const auto &source = *source_pointer;
|
||||||
|
const bool historical = lineage.historical.count(
|
||||||
|
{source.feature_set_id, source.feature_index}) != 0;
|
||||||
|
auto registered = registered_component.find(source.image_id);
|
||||||
|
if (registered == registered_component.end() ||
|
||||||
|
registered->second != lineage.component_key) {
|
||||||
|
if (!historical)
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_DESCENDANT_UNREGISTERED_OBSERVATION;
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
}
|
||||||
|
const size_t landmark_index = landmark_by_track.at(lineage.extension_track_id);
|
||||||
|
observations.push_back({landmarks[landmark_index].landmark_id,
|
||||||
|
lineage.extension_track_id, source.image_id,
|
||||||
|
source.feature_set_id, source.feature_index, position++});
|
||||||
|
++landmarks[landmark_index].observation_count;
|
||||||
|
if (!historical) added = true;
|
||||||
|
}
|
||||||
|
if (added) affected.insert(lineage.component_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t next_landmark_id = 1;
|
||||||
|
for (const auto &landmark : landmarks) {
|
||||||
|
if (landmark.landmark_id >= next_landmark_id) {
|
||||||
|
if (landmark.landmark_id >= UINT64_MAX - 1)
|
||||||
|
next_landmark_id = UINT64_MAX;
|
||||||
|
else
|
||||||
|
next_landmark_id = landmark.landmark_id + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::map<uint64_t, const Lardon3DSparseIncrementalImage *> images_by_id;
|
||||||
|
for (size_t index = 0; index < input.extension->image_count; ++index)
|
||||||
|
images_by_id.emplace(input.extension->images[index].image_id,
|
||||||
|
&input.extension->images[index]);
|
||||||
|
for (const auto &[track_id, track] : extension_tracks) {
|
||||||
|
if (extension_lineage.count(track_id)) continue;
|
||||||
|
if (track.size() < 2) continue;
|
||||||
|
uint64_t component_key = 0;
|
||||||
|
bool admissible = true;
|
||||||
|
std::vector<Lardon3DSparseGeometryPoint2> normalized;
|
||||||
|
std::vector<Lardon3DSparseGeometryPose> poses;
|
||||||
|
normalized.reserve(track.size());
|
||||||
|
poses.reserve(track.size());
|
||||||
|
for (const auto *source : track) {
|
||||||
|
auto registered = registered_component.find(source->image_id);
|
||||||
|
auto image_entry = images_by_id.find(source->image_id);
|
||||||
|
const auto *image = image_entry == images_by_id.end() ? nullptr
|
||||||
|
: image_entry->second;
|
||||||
|
auto camera = std::lower_bound(cameras.begin(), cameras.end(), source->image_id,
|
||||||
|
[](const auto &item, uint64_t id) {
|
||||||
|
return item.image_id < id;
|
||||||
|
});
|
||||||
|
if (registered == registered_component.end() || !image || camera == cameras.end() ||
|
||||||
|
camera->image_id != source->image_id ||
|
||||||
|
(component_key != 0 && component_key != registered->second)) {
|
||||||
|
admissible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
component_key = registered->second;
|
||||||
|
Lardon3DSparseGeometryPoint2 pixel{source->x, source->y};
|
||||||
|
Lardon3DSparseGeometryPoint2 point{};
|
||||||
|
if (lardon3d_sparse_geometry_normalize(&image->calibration, &pixel, 1, &point) !=
|
||||||
|
LARDON3D_SPARSE_GEOMETRY_OK) {
|
||||||
|
admissible = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
normalized.push_back(point);
|
||||||
|
poses.push_back(camera->pose_cw);
|
||||||
|
}
|
||||||
|
if (!admissible) continue;
|
||||||
|
Lardon3DSparseGeometryPoint3 point{};
|
||||||
|
if (lardon3d_sparse_geometry_triangulate_multi_view(
|
||||||
|
normalized.data(), poses.data(), poses.size(), &point) !=
|
||||||
|
LARDON3D_SPARSE_GEOMETRY_OK)
|
||||||
|
continue;
|
||||||
|
Lardon3DSparseGeometryPoint3 refined{};
|
||||||
|
if (lardon3d_sparse_geometry_refine_point(
|
||||||
|
normalized.data(), poses.data(), poses.size(), &point,
|
||||||
|
&input.parameters->refinement, &refined) == LARDON3D_SPARSE_GEOMETRY_OK)
|
||||||
|
point = refined;
|
||||||
|
bool accepted = finite_point(point);
|
||||||
|
for (size_t index = 0; accepted && index < track.size(); ++index) {
|
||||||
|
auto image_entry = images_by_id.find(track[index]->image_id);
|
||||||
|
const auto *image = image_entry == images_by_id.end() ? nullptr
|
||||||
|
: image_entry->second;
|
||||||
|
accepted = image && project_ok(image->calibration, poses[index], point,
|
||||||
|
{track[index]->x, track[index]->y},
|
||||||
|
input.parameters->reprojection_threshold_px);
|
||||||
|
}
|
||||||
|
if (!accepted) continue;
|
||||||
|
if (next_landmark_id == 0 || next_landmark_id == UINT64_MAX)
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE;
|
||||||
|
const uint64_t landmark_id = next_landmark_id++;
|
||||||
|
landmarks.push_back({landmark_id, track_id, component_key, point, 0.0, 0.0,
|
||||||
|
static_cast<uint64_t>(track.size())});
|
||||||
|
uint32_t position = 0;
|
||||||
|
for (const auto *source : track)
|
||||||
|
observations.push_back({landmark_id, track_id, source->image_id,
|
||||||
|
source->feature_set_id, source->feature_index, position++});
|
||||||
|
affected.insert(component_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (affected.empty()) {
|
||||||
|
result->status = LARDON3D_INCREMENTAL_RECONSTRUCTION_NO_CHANGE;
|
||||||
|
return result->status;
|
||||||
|
}
|
||||||
|
std::sort(landmarks.begin(), landmarks.end(), [](const auto &left, const auto &right) {
|
||||||
|
return left.track_id < right.track_id;
|
||||||
|
});
|
||||||
|
std::sort(observations.begin(), observations.end(), [](const auto &left, const auto &right) {
|
||||||
|
return std::tie(left.landmark_id, left.position_in_track) <
|
||||||
|
std::tie(right.landmark_id, right.position_in_track);
|
||||||
|
});
|
||||||
|
std::map<uint64_t, uint64_t> component_camera_counts;
|
||||||
|
std::map<uint64_t, uint64_t> component_landmark_counts;
|
||||||
|
for (const auto &camera : cameras)
|
||||||
|
++component_camera_counts[camera.component_key];
|
||||||
|
for (const auto &landmark : landmarks)
|
||||||
|
++component_landmark_counts[landmark.component_key];
|
||||||
|
for (auto &component : components) {
|
||||||
|
component.registered_image_count = component_camera_counts[component.component_key];
|
||||||
|
component.image_count = component.registered_image_count;
|
||||||
|
component.landmark_count = component_landmark_counts[component.component_key];
|
||||||
|
}
|
||||||
|
if (!checkpoint(input)) return LARDON3D_INCREMENTAL_RECONSTRUCTION_CANCELLED;
|
||||||
|
if (!full_ba_affected(input, affected, &components, &cameras, &landmarks,
|
||||||
|
&observations))
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_BUNDLE_ADJUSTMENT_FAILED;
|
||||||
|
if (!checkpoint(input)) return LARDON3D_INCREMENTAL_RECONSTRUCTION_CANCELLED;
|
||||||
|
|
||||||
|
Lardon3DSparseIncrementalResult snapshot{};
|
||||||
|
snapshot.status = LARDON3D_SPARSE_INCREMENTAL_COMPLETE;
|
||||||
|
snapshot.track_set_id = input.extension->track_set_id;
|
||||||
|
snapshot.calibration_scope_id = input.extension->calibration_scope_id;
|
||||||
|
snapshot.component_count = components.size();
|
||||||
|
snapshot.camera_count = cameras.size();
|
||||||
|
snapshot.landmark_count = landmarks.size();
|
||||||
|
snapshot.observation_count = observations.size();
|
||||||
|
if (!copy_array(components, &snapshot.components) ||
|
||||||
|
!copy_array(cameras, &snapshot.cameras) ||
|
||||||
|
!copy_array(landmarks, &snapshot.landmarks) ||
|
||||||
|
!copy_array(observations, &snapshot.observations)) {
|
||||||
|
destroy_snapshot(&snapshot);
|
||||||
|
return LARDON3D_INCREMENTAL_RECONSTRUCTION_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
result->snapshot = snapshot;
|
||||||
|
result->changed = true;
|
||||||
|
result->status = LARDON3D_INCREMENTAL_RECONSTRUCTION_OK;
|
||||||
|
return result->status;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
extern "C" Lardon3DIncrementalReconstructionStatus
|
||||||
|
lardon3d_incremental_reconstruction_run(
|
||||||
|
const Lardon3DIncrementalReconstructionInput *input,
|
||||||
|
Lardon3DIncrementalReconstructionResult *result) {
|
||||||
|
if (!result) return LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_ARGUMENT;
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(result);
|
||||||
|
if (!input) {
|
||||||
|
result->status = LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_ARGUMENT;
|
||||||
|
return result->status;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return execute(*input, result);
|
||||||
|
} catch (const std::bad_alloc &) {
|
||||||
|
result->status = LARDON3D_INCREMENTAL_RECONSTRUCTION_OUT_OF_MEMORY;
|
||||||
|
return result->status;
|
||||||
|
} catch (...) {
|
||||||
|
result->status = LARDON3D_INCREMENTAL_RECONSTRUCTION_GEOMETRY_FAILED;
|
||||||
|
return result->status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void lardon3d_incremental_reconstruction_result_destroy(
|
||||||
|
Lardon3DIncrementalReconstructionResult *result) {
|
||||||
|
if (!result) return;
|
||||||
|
destroy_snapshot(&result->snapshot);
|
||||||
|
*result = {};
|
||||||
|
}
|
||||||
122
src/incremental_reconstruction_identity.cpp
Normal file
122
src/incremental_reconstruction_identity.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#include <lardon3d/incremental_reconstruction.h>
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr size_t fingerprint_size =
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_FINGERPRINT_RECORD_SIZE;
|
||||||
|
constexpr size_t identity_size =
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_IDENTITY_RECORD_SIZE;
|
||||||
|
|
||||||
|
bool put_u32(unsigned char *record, size_t capacity, size_t *offset,
|
||||||
|
uint32_t value) {
|
||||||
|
if (!record || !offset || *offset > capacity - 4) return false;
|
||||||
|
for (unsigned int byte = 0; byte < 4; ++byte)
|
||||||
|
record[(*offset)++] = static_cast<unsigned char>(value >> (byte * 8U));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool put_u64(unsigned char *record, size_t capacity, size_t *offset,
|
||||||
|
uint64_t value) {
|
||||||
|
if (!record || !offset || *offset > capacity - 8) return false;
|
||||||
|
for (unsigned int byte = 0; byte < 8; ++byte)
|
||||||
|
record[(*offset)++] = static_cast<unsigned char>(value >> (byte * 8U));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool digest_record(const unsigned char *record, size_t size,
|
||||||
|
unsigned char digest[32]) {
|
||||||
|
unsigned int length = 0;
|
||||||
|
return record && digest &&
|
||||||
|
EVP_Digest(record, size, digest, &length, EVP_sha256(), nullptr) == 1 &&
|
||||||
|
length == 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checked_add(uint64_t left, uint64_t right, uint64_t *output) {
|
||||||
|
if (!output || right > std::numeric_limits<uint64_t>::max() - left) return false;
|
||||||
|
*output = left + right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checked_term(uint64_t count, uint64_t coefficient, uint64_t *raw) {
|
||||||
|
if (count != 0 && coefficient > std::numeric_limits<uint64_t>::max() / count)
|
||||||
|
return false;
|
||||||
|
return checked_add(*raw, count * coefficient, raw);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_incremental_reconstruction_parameter_fingerprint_record(
|
||||||
|
unsigned char record[fingerprint_size]) {
|
||||||
|
if (!record) return false;
|
||||||
|
std::memset(record, 0, fingerprint_size);
|
||||||
|
std::memcpy(record, "L3DHPRM1", 8);
|
||||||
|
size_t offset = 8;
|
||||||
|
if (!put_u32(record, fingerprint_size, &offset, 1) ||
|
||||||
|
!put_u32(record, fingerprint_size, &offset,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_VERSION))
|
||||||
|
return false;
|
||||||
|
/* Field position is the policy identifier; every v1 policy is version 1. */
|
||||||
|
for (unsigned int policy = 0; policy < 16; ++policy)
|
||||||
|
if (!put_u32(record, fingerprint_size, &offset, 1)) return false;
|
||||||
|
return offset == fingerprint_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_incremental_reconstruction_parameter_fingerprint(
|
||||||
|
unsigned char digest[32]) {
|
||||||
|
unsigned char record[fingerprint_size];
|
||||||
|
return digest &&
|
||||||
|
lardon3d_incremental_reconstruction_parameter_fingerprint_record(record) &&
|
||||||
|
digest_record(record, sizeof(record), digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_incremental_reconstruction_identity_record(
|
||||||
|
const Lardon3DIncrementalReconstructionIdentity *identity,
|
||||||
|
unsigned char record[identity_size]) {
|
||||||
|
if (!identity || !record || identity->base_reconstruction_id == 0 ||
|
||||||
|
identity->extension_track_set_id == 0 || identity->calibration_scope_id == 0 ||
|
||||||
|
identity->incremental_kind != LARDON3D_INCREMENTAL_RECONSTRUCTION_KIND ||
|
||||||
|
identity->incremental_version != LARDON3D_INCREMENTAL_RECONSTRUCTION_VERSION)
|
||||||
|
return false;
|
||||||
|
std::memset(record, 0, identity_size);
|
||||||
|
std::memcpy(record, "L3DHIDV1", 8);
|
||||||
|
size_t offset = 8;
|
||||||
|
if (!put_u32(record, identity_size, &offset, 1) ||
|
||||||
|
!put_u64(record, identity_size, &offset, identity->base_reconstruction_id) ||
|
||||||
|
!put_u64(record, identity_size, &offset, identity->extension_track_set_id) ||
|
||||||
|
!put_u64(record, identity_size, &offset, identity->calibration_scope_id) ||
|
||||||
|
!put_u32(record, identity_size, &offset, identity->incremental_kind) ||
|
||||||
|
!put_u32(record, identity_size, &offset, identity->incremental_version))
|
||||||
|
return false;
|
||||||
|
std::memcpy(record + offset, identity->parameter_fingerprint, 32);
|
||||||
|
offset += 32;
|
||||||
|
return offset == identity_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_incremental_reconstruction_identity_digest(
|
||||||
|
const Lardon3DIncrementalReconstructionIdentity *identity,
|
||||||
|
unsigned char digest[32]) {
|
||||||
|
unsigned char record[identity_size];
|
||||||
|
return digest && lardon3d_incremental_reconstruction_identity_record(identity, record) &&
|
||||||
|
digest_record(record, sizeof(record), digest);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_incremental_reconstruction_resource_estimate(
|
||||||
|
const Lardon3DIncrementalReconstructionShape *shape,
|
||||||
|
Lardon3DResourceEstimate *estimate) {
|
||||||
|
if (!shape || !estimate) return false;
|
||||||
|
uint64_t raw = 268435456ULL;
|
||||||
|
if (!checked_term(shape->base_camera_count, 131072ULL, &raw) ||
|
||||||
|
!checked_term(shape->base_landmark_count, 4096ULL, &raw) ||
|
||||||
|
!checked_term(shape->base_observation_count, 1024ULL, &raw) ||
|
||||||
|
!checked_term(shape->extension_image_count, 131072ULL, &raw) ||
|
||||||
|
!checked_term(shape->extension_track_count, 4096ULL, &raw) ||
|
||||||
|
!checked_term(shape->extension_observation_count, 1024ULL, &raw))
|
||||||
|
return false;
|
||||||
|
constexpr uint64_t mib = 1024ULL * 1024ULL;
|
||||||
|
const uint64_t remainder = raw % mib;
|
||||||
|
if (remainder != 0 && !checked_add(raw, mib - remainder, &raw)) return false;
|
||||||
|
*estimate = {raw, 0, 0, 0, 1, 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_CPU};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
22
src/incremental_reconstruction_internal.h
Normal file
22
src/incremental_reconstruction_internal.h
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
#ifndef LARDON3D_INCREMENTAL_RECONSTRUCTION_INTERNAL_H
|
||||||
|
#define LARDON3D_INCREMENTAL_RECONSTRUCTION_INTERNAL_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <lardon3d/sparse_sfm_bundle_adjustment.h>
|
||||||
|
|
||||||
|
struct Lardon3DSparseBundleAdjustmentAnchor {
|
||||||
|
uint64_t component_key;
|
||||||
|
uint64_t pose_anchor_image_id;
|
||||||
|
uint64_t scale_anchor_image_id;
|
||||||
|
Lardon3DSparseBundleAdjustmentScaleAxis scale_axis;
|
||||||
|
};
|
||||||
|
|
||||||
|
Lardon3DSparseBundleAdjustmentExecutionStatus
|
||||||
|
lardon3d_sparse_bundle_adjustment_run_with_anchors(
|
||||||
|
const Lardon3DSparseBundleAdjustmentInput *input,
|
||||||
|
const Lardon3DSparseBundleAdjustmentAnchor *anchors, size_t anchor_count,
|
||||||
|
Lardon3DSparseBundleAdjustmentResult *result);
|
||||||
|
|
||||||
|
#endif
|
||||||
764
src/incremental_reconstruction_task.cpp
Normal file
764
src/incremental_reconstruction_task.cpp
Normal file
|
|
@ -0,0 +1,764 @@
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/app_state.h>
|
||||||
|
#include <lardon3d/incremental_reconstruction_task.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <map>
|
||||||
|
#include <new>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "sparse_sfm_gate_f_internal.h"
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/feature_store.h>
|
||||||
|
#include <lardon3d/incremental_reconstruction.h>
|
||||||
|
#include <lardon3d/project.h>
|
||||||
|
#include <lardon3d/sparse_sfm_model.h>
|
||||||
|
#include <lardon3d/task_checkpoint.h>
|
||||||
|
#include <lardon3d/task_queue.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct Context {
|
||||||
|
std::string project_path;
|
||||||
|
Lardon3DProjectDb *database = nullptr;
|
||||||
|
Lardon3DProjectDbIncrementalReconstructionTask durable{};
|
||||||
|
Lardon3DIncrementalReconstructionShape shape{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Reader {
|
||||||
|
Lardon3DProjectDbFeatureSet feature_set{};
|
||||||
|
Lardon3DFeatureReader *reader = nullptr;
|
||||||
|
Lardon3DFeatureFileMetadata metadata{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Resolved {
|
||||||
|
Lardon3DSparseReconstruction base_record{};
|
||||||
|
Lardon3DSparseIncrementalResult base{};
|
||||||
|
std::vector<Lardon3DSparseIncrementalComponent> base_components;
|
||||||
|
std::vector<Lardon3DSparseIncrementalCamera> base_cameras;
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmark> base_landmarks;
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmarkObservation> base_observations;
|
||||||
|
std::vector<Lardon3DSparseIncrementalObservation> base_track_observations;
|
||||||
|
std::vector<Lardon3DSparseIncrementalImage> extension_images;
|
||||||
|
std::vector<Lardon3DSparseIncrementalObservation> extension_observations;
|
||||||
|
std::map<uint64_t, Lardon3DSparseGeometryCalibration> calibrations;
|
||||||
|
};
|
||||||
|
|
||||||
|
void destroy_context(void *userdata) { delete static_cast<Context *>(userdata); }
|
||||||
|
|
||||||
|
bool valid_durable(const Lardon3DProjectDbIncrementalReconstructionTask &durable) {
|
||||||
|
unsigned char expected[32];
|
||||||
|
return durable.task_id != 0 && durable.base_reconstruction_id != 0 &&
|
||||||
|
durable.extension_track_set_id != 0 && durable.calibration_scope_id != 0 &&
|
||||||
|
durable.incremental_kind == LARDON3D_INCREMENTAL_RECONSTRUCTION_KIND &&
|
||||||
|
durable.incremental_version == LARDON3D_INCREMENTAL_RECONSTRUCTION_VERSION &&
|
||||||
|
lardon3d_incremental_reconstruction_parameter_fingerprint(expected) &&
|
||||||
|
std::memcmp(expected, durable.parameter_fingerprint, 32) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checked_add(uint64_t left, uint64_t right, uint64_t *output) {
|
||||||
|
if (!output || right > UINT64_MAX - left) return false;
|
||||||
|
*output = left + right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_scope(Lardon3DProjectDb *database, uint64_t scope_id,
|
||||||
|
std::map<uint64_t, Lardon3DSparseCalibrationMember> *members,
|
||||||
|
std::map<uint64_t, Lardon3DSparseGeometryCalibration> *calibrations) {
|
||||||
|
Lardon3DSparseCalibrationScope scope{};
|
||||||
|
if (!database || !members ||
|
||||||
|
lardon3d_sparse_calibration_scope_load(database, scope_id, &scope) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
uint64_t after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseCalibrationMember page[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
size_t count = 0;
|
||||||
|
uint64_t next = 0;
|
||||||
|
if (lardon3d_sparse_calibration_scope_list_members(
|
||||||
|
database, scope_id, after, page, LARDON3D_SPARSE_SFM_PAGE_MAX,
|
||||||
|
&count, &next) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < count; ++index) {
|
||||||
|
if (!members->emplace(page[index].image_id, page[index]).second) return false;
|
||||||
|
if (calibrations) {
|
||||||
|
Lardon3DSparseCalibration calibration{};
|
||||||
|
if (lardon3d_sparse_calibration_load(database, page[index].calibration_id,
|
||||||
|
&calibration) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK ||
|
||||||
|
std::memcmp(calibration.scientific_hash,
|
||||||
|
page[index].calibration_hash, 32) != 0)
|
||||||
|
return false;
|
||||||
|
calibrations->emplace(
|
||||||
|
page[index].image_id,
|
||||||
|
Lardon3DSparseGeometryCalibration{
|
||||||
|
calibration.width, calibration.height, calibration.fx,
|
||||||
|
calibration.fy, calibration.cx, calibration.cy, calibration.k1,
|
||||||
|
calibration.k2, calibration.p1, calibration.p2});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count == 0) break;
|
||||||
|
if (next <= after) return false;
|
||||||
|
after = next;
|
||||||
|
}
|
||||||
|
return members->size() == scope.member_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool derive_shape(Context *context) {
|
||||||
|
if (!context) return false;
|
||||||
|
Lardon3DSparseReconstruction base{};
|
||||||
|
if (lardon3d_sparse_reconstruction_load(
|
||||||
|
context->database, context->durable.base_reconstruction_id, &base) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
Lardon3DProjectDbTrackSet track_set{};
|
||||||
|
if (lardon3d_project_db_load_track_set(
|
||||||
|
context->database, context->durable.extension_track_set_id,
|
||||||
|
&track_set) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
std::map<uint64_t, Lardon3DSparseCalibrationMember> members;
|
||||||
|
if (!load_scope(context->database, context->durable.calibration_scope_id,
|
||||||
|
&members, nullptr))
|
||||||
|
return false;
|
||||||
|
uint64_t observations = 0;
|
||||||
|
uint64_t tracks_seen = 0;
|
||||||
|
uint64_t after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DProjectDbTrack tracks[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
size_t count = 0;
|
||||||
|
if (lardon3d_project_db_list_tracks(
|
||||||
|
context->database, track_set.track_set_id, after, tracks,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &count) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
bool valid = true;
|
||||||
|
for (size_t index = 0; index < count; ++index) {
|
||||||
|
if (tracks[index].track_id <= after ||
|
||||||
|
!checked_add(observations, tracks[index].observation_count,
|
||||||
|
&observations))
|
||||||
|
valid = false;
|
||||||
|
after = tracks[index].track_id;
|
||||||
|
++tracks_seen;
|
||||||
|
lardon3d_project_db_free_track(&tracks[index]);
|
||||||
|
}
|
||||||
|
if (!valid) return false;
|
||||||
|
if (count == 0) break;
|
||||||
|
}
|
||||||
|
if (tracks_seen != track_set.track_count || observations == 0) return false;
|
||||||
|
context->shape = {base.registered_image_count, base.landmark_count,
|
||||||
|
0, members.size(), tracks_seen, observations};
|
||||||
|
uint64_t landmark_after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseLandmark page[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
Lardon3DSparseLandmarkPage list{landmark_after,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, 0, 0, page};
|
||||||
|
if (lardon3d_sparse_landmark_list(
|
||||||
|
context->database, base.reconstruction_id, landmark_after,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &list) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < list.count; ++index)
|
||||||
|
if (!checked_add(context->shape.base_observation_count,
|
||||||
|
page[index].observation_count,
|
||||||
|
&context->shape.base_observation_count))
|
||||||
|
return false;
|
||||||
|
if (list.count == 0) break;
|
||||||
|
if (list.next_track_id <= landmark_after) return false;
|
||||||
|
landmark_after = list.next_track_id;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_readers(std::map<uint64_t, Reader> *readers) {
|
||||||
|
for (auto &[id, reader] : *readers) {
|
||||||
|
(void)id;
|
||||||
|
if (reader.reader) lardon3d_feature_reader_close(reader.reader);
|
||||||
|
}
|
||||||
|
readers->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool resolve_observation(Context *context,
|
||||||
|
const Lardon3DProjectDbTrackObservation &source,
|
||||||
|
std::map<uint64_t, Reader> *readers, uint64_t track_id,
|
||||||
|
Lardon3DSparseIncrementalObservation *output) {
|
||||||
|
auto found = readers->find(source.feature_set_id);
|
||||||
|
if (found == readers->end()) {
|
||||||
|
Reader reader;
|
||||||
|
if (lardon3d_project_db_load_feature_set(
|
||||||
|
context->database, source.feature_set_id, &reader.feature_set) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK ||
|
||||||
|
lardon3d_feature_reader_open(context->project_path.c_str(),
|
||||||
|
&reader.feature_set, &reader.reader,
|
||||||
|
&reader.metadata) != LARDON3D_FEATURE_STORE_OK ||
|
||||||
|
reader.metadata.feature_count != reader.feature_set.feature_count) {
|
||||||
|
if (reader.reader) lardon3d_feature_reader_close(reader.reader);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
found = readers->emplace(source.feature_set_id, reader).first;
|
||||||
|
}
|
||||||
|
Lardon3DFeatureKeypoint keypoint{};
|
||||||
|
if (source.feature_index >= found->second.metadata.feature_count ||
|
||||||
|
lardon3d_feature_reader_keypoints(found->second.reader, source.feature_index,
|
||||||
|
&keypoint, 1) != LARDON3D_FEATURE_STORE_OK ||
|
||||||
|
!std::isfinite(keypoint.x) || !std::isfinite(keypoint.y))
|
||||||
|
return false;
|
||||||
|
*output = {track_id, found->second.feature_set.image_id, source.feature_set_id,
|
||||||
|
source.feature_index, found->second.metadata.feature_count,
|
||||||
|
static_cast<double>(keypoint.x), static_cast<double>(keypoint.y)};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_base_snapshot(Context *context, Resolved *resolved,
|
||||||
|
std::map<uint64_t, Reader> *readers) {
|
||||||
|
if (lardon3d_sparse_reconstruction_load(
|
||||||
|
context->database, context->durable.base_reconstruction_id,
|
||||||
|
&resolved->base_record) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
uint64_t after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseComponent page[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
Lardon3DSparseComponentPage list{after, LARDON3D_SPARSE_SFM_PAGE_MAX,
|
||||||
|
0, 0, page};
|
||||||
|
if (lardon3d_sparse_component_list(
|
||||||
|
context->database, resolved->base_record.reconstruction_id, after,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &list) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < list.count; ++index)
|
||||||
|
resolved->base_components.push_back(
|
||||||
|
{page[index].component_key, page[index].registered_image_count,
|
||||||
|
page[index].registered_image_count, page[index].landmark_count});
|
||||||
|
if (list.count == 0) break;
|
||||||
|
if (list.next_component_key <= after) return false;
|
||||||
|
after = list.next_component_key;
|
||||||
|
}
|
||||||
|
after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseRegisteredImage page[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
Lardon3DSparseRegisteredImagePage list{after, LARDON3D_SPARSE_SFM_PAGE_MAX,
|
||||||
|
0, 0, page};
|
||||||
|
if (lardon3d_sparse_registered_image_list(
|
||||||
|
context->database, resolved->base_record.reconstruction_id, after,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &list) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < list.count; ++index) {
|
||||||
|
Lardon3DSparseGeometryPose pose{};
|
||||||
|
std::memcpy(pose.rotation_cw, page[index].rotation_cw,
|
||||||
|
sizeof(pose.rotation_cw));
|
||||||
|
std::memcpy(pose.translation_cw, page[index].translation_cw,
|
||||||
|
sizeof(pose.translation_cw));
|
||||||
|
resolved->base_cameras.push_back(
|
||||||
|
{page[index].image_id, page[index].component_key, pose});
|
||||||
|
}
|
||||||
|
if (list.count == 0) break;
|
||||||
|
if (list.next_image_id <= after) return false;
|
||||||
|
after = list.next_image_id;
|
||||||
|
}
|
||||||
|
after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseLandmark page[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
Lardon3DSparseLandmarkPage list{after, LARDON3D_SPARSE_SFM_PAGE_MAX,
|
||||||
|
0, 0, page};
|
||||||
|
if (lardon3d_sparse_landmark_list(
|
||||||
|
context->database, resolved->base_record.reconstruction_id, after,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &list) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < list.count; ++index) {
|
||||||
|
resolved->base_landmarks.push_back(
|
||||||
|
{page[index].landmark_id, page[index].track_id,
|
||||||
|
page[index].component_key, {page[index].x, page[index].y, page[index].z},
|
||||||
|
page[index].reprojection_rmse_px,
|
||||||
|
page[index].reprojection_median_px, page[index].observation_count});
|
||||||
|
Lardon3DProjectDbTrack track{};
|
||||||
|
if (lardon3d_project_db_load_track(context->database, page[index].track_id,
|
||||||
|
&track) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
bool valid = true;
|
||||||
|
for (uint32_t position = 0; position < track.observation_count; ++position) {
|
||||||
|
Lardon3DSparseIncrementalObservation observation{};
|
||||||
|
if (!resolve_observation(context, track.observations[position], readers,
|
||||||
|
track.track_id, &observation)) {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
resolved->base_track_observations.push_back(observation);
|
||||||
|
}
|
||||||
|
lardon3d_project_db_free_track(&track);
|
||||||
|
if (!valid) return false;
|
||||||
|
}
|
||||||
|
if (list.count == 0) break;
|
||||||
|
if (list.next_track_id <= after) return false;
|
||||||
|
after = list.next_track_id;
|
||||||
|
}
|
||||||
|
uint64_t after_landmark = 0;
|
||||||
|
uint32_t after_position = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseLandmarkObservation page[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
Lardon3DSparseObservationPage list{after_landmark, after_position,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, 0, 0, 0,
|
||||||
|
page};
|
||||||
|
if (lardon3d_sparse_observation_list(
|
||||||
|
context->database, resolved->base_record.reconstruction_id,
|
||||||
|
after_landmark, after_position, LARDON3D_SPARSE_SFM_PAGE_MAX,
|
||||||
|
&list) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < list.count; ++index) {
|
||||||
|
auto reader = readers->find(page[index].feature_set_id);
|
||||||
|
if (reader == readers->end()) {
|
||||||
|
Reader loaded;
|
||||||
|
if (lardon3d_project_db_load_feature_set(
|
||||||
|
context->database, page[index].feature_set_id,
|
||||||
|
&loaded.feature_set) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
reader = readers->emplace(page[index].feature_set_id, loaded).first;
|
||||||
|
}
|
||||||
|
resolved->base_observations.push_back(
|
||||||
|
{page[index].landmark_id, page[index].track_id,
|
||||||
|
reader->second.feature_set.image_id,
|
||||||
|
page[index].feature_set_id, page[index].feature_index,
|
||||||
|
page[index].position_in_track});
|
||||||
|
}
|
||||||
|
if (list.count == 0) break;
|
||||||
|
if (list.next_landmark_id < after_landmark ||
|
||||||
|
(list.next_landmark_id == after_landmark &&
|
||||||
|
list.next_position_in_track <= after_position))
|
||||||
|
return false;
|
||||||
|
after_landmark = list.next_landmark_id;
|
||||||
|
after_position = list.next_position_in_track;
|
||||||
|
}
|
||||||
|
resolved->base.status = LARDON3D_SPARSE_INCREMENTAL_COMPLETE;
|
||||||
|
resolved->base.track_set_id = resolved->base_record.track_set_id;
|
||||||
|
resolved->base.calibration_scope_id = resolved->base_record.calibration_scope_id;
|
||||||
|
resolved->base.components = resolved->base_components.data();
|
||||||
|
resolved->base.component_count = resolved->base_components.size();
|
||||||
|
resolved->base.cameras = resolved->base_cameras.data();
|
||||||
|
resolved->base.camera_count = resolved->base_cameras.size();
|
||||||
|
resolved->base.landmarks = resolved->base_landmarks.data();
|
||||||
|
resolved->base.landmark_count = resolved->base_landmarks.size();
|
||||||
|
resolved->base.observations = resolved->base_observations.data();
|
||||||
|
resolved->base.observation_count = resolved->base_observations.size();
|
||||||
|
return resolved->base.component_count == resolved->base_record.component_count &&
|
||||||
|
resolved->base.camera_count == resolved->base_record.registered_image_count &&
|
||||||
|
resolved->base.landmark_count == resolved->base_record.landmark_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool resolve_all(Context *context, Resolved *resolved) {
|
||||||
|
std::map<uint64_t, Reader> readers;
|
||||||
|
bool valid = load_base_snapshot(context, resolved, &readers);
|
||||||
|
std::map<uint64_t, Lardon3DSparseCalibrationMember> base_scope;
|
||||||
|
std::map<uint64_t, Lardon3DSparseCalibrationMember> extension_scope;
|
||||||
|
if (valid)
|
||||||
|
valid = load_scope(context->database, resolved->base_record.calibration_scope_id,
|
||||||
|
&base_scope, nullptr) &&
|
||||||
|
load_scope(context->database, context->durable.calibration_scope_id,
|
||||||
|
&extension_scope, &resolved->calibrations);
|
||||||
|
for (const auto &camera : resolved->base_cameras) {
|
||||||
|
auto historical = base_scope.find(camera.image_id);
|
||||||
|
auto extension = extension_scope.find(camera.image_id);
|
||||||
|
if (!valid || historical == base_scope.end() || extension == extension_scope.end() ||
|
||||||
|
std::memcmp(historical->second.calibration_hash,
|
||||||
|
extension->second.calibration_hash, 32) != 0) {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Lardon3DProjectDbTrackSet track_set{};
|
||||||
|
if (valid)
|
||||||
|
valid = lardon3d_project_db_load_track_set(
|
||||||
|
context->database, context->durable.extension_track_set_id,
|
||||||
|
&track_set) == LARDON3D_PROJECT_DB_OK;
|
||||||
|
uint64_t after = 0;
|
||||||
|
uint64_t tracks_seen = 0;
|
||||||
|
std::set<uint64_t> participating_images;
|
||||||
|
while (valid) {
|
||||||
|
Lardon3DProjectDbTrack tracks[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
size_t count = 0;
|
||||||
|
if (lardon3d_project_db_list_tracks(
|
||||||
|
context->database, track_set.track_set_id, after, tracks,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &count) != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; valid && index < count; ++index) {
|
||||||
|
if (tracks[index].track_id <= after) {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (uint32_t position = 0;
|
||||||
|
valid && position < tracks[index].observation_count; ++position) {
|
||||||
|
Lardon3DSparseIncrementalObservation observation{};
|
||||||
|
valid = resolve_observation(context, tracks[index].observations[position],
|
||||||
|
&readers, tracks[index].track_id, &observation) &&
|
||||||
|
resolved->calibrations.count(observation.image_id) != 0;
|
||||||
|
if (valid) {
|
||||||
|
participating_images.insert(observation.image_id);
|
||||||
|
resolved->extension_observations.push_back(observation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
after = tracks[index].track_id;
|
||||||
|
++tracks_seen;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < count; ++index)
|
||||||
|
lardon3d_project_db_free_track(&tracks[index]);
|
||||||
|
if (!valid || count == 0) break;
|
||||||
|
}
|
||||||
|
for (const auto &camera : resolved->base_cameras)
|
||||||
|
participating_images.insert(camera.image_id);
|
||||||
|
for (uint64_t image_id : participating_images) {
|
||||||
|
auto calibration = resolved->calibrations.find(image_id);
|
||||||
|
if (calibration == resolved->calibrations.end()) {
|
||||||
|
valid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
resolved->extension_images.push_back({image_id, calibration->second});
|
||||||
|
}
|
||||||
|
close_readers(&readers);
|
||||||
|
return valid && tracks_seen == track_set.track_count &&
|
||||||
|
!resolved->extension_observations.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool persist(Context *context, const Lardon3DTask *task) {
|
||||||
|
Lardon3DTaskDurableSnapshot snapshot{};
|
||||||
|
if (!lardon3d_task_durable_snapshot(task, &snapshot)) return false;
|
||||||
|
Lardon3DProjectDbCheckpoint checkpoint_record{};
|
||||||
|
std::snprintf(checkpoint_record.path, sizeof(checkpoint_record.path),
|
||||||
|
".lardon3d/checkpoints/%llu.chk",
|
||||||
|
static_cast<unsigned long long>(snapshot.id));
|
||||||
|
checkpoint_record.format_version = LARDON3D_TASK_CHECKPOINT_VERSION;
|
||||||
|
std::string path = context->project_path + "/" + checkpoint_record.path;
|
||||||
|
auto saved = lardon3d_task_checkpoint_save(path.c_str(), &snapshot);
|
||||||
|
if (saved != LARDON3D_TASK_CHECKPOINT_OK &&
|
||||||
|
saved != LARDON3D_TASK_CHECKPOINT_PUBLISHED_NOT_DURABLE)
|
||||||
|
return false;
|
||||||
|
checkpoint_record.durability =
|
||||||
|
saved == LARDON3D_TASK_CHECKPOINT_OK
|
||||||
|
? LARDON3D_DB_CHECKPOINT_DURABLE
|
||||||
|
: LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE;
|
||||||
|
return lardon3d_project_db_record_incremental_reconstruction_task(
|
||||||
|
context->database, &snapshot,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND_VERSION,
|
||||||
|
&checkpoint_record, &context->durable, 0) == LARDON3D_PROJECT_DB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool task_checkpoint(void *userdata) {
|
||||||
|
return lardon3d_task_checkpoint(static_cast<Lardon3DTask *>(userdata));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_publication(const Resolved &resolved,
|
||||||
|
const Lardon3DSparseIncrementalResult &snapshot,
|
||||||
|
const unsigned char parameter_fingerprint[32],
|
||||||
|
std::vector<Lardon3DSparseComponent> *components,
|
||||||
|
std::vector<Lardon3DSparseRegisteredImage> *cameras,
|
||||||
|
std::vector<Lardon3DSparseLandmark> *landmarks,
|
||||||
|
std::vector<Lardon3DSparseLandmarkObservation> *observations,
|
||||||
|
Lardon3DSparsePublication *publication) {
|
||||||
|
std::map<uint64_t, const Lardon3DSparseIncrementalCamera *> cameras_by_image;
|
||||||
|
std::map<uint64_t, const Lardon3DSparseIncrementalLandmark *> landmarks_by_track;
|
||||||
|
std::map<std::pair<uint64_t, uint32_t>,
|
||||||
|
const Lardon3DSparseIncrementalObservation *>
|
||||||
|
source_observations;
|
||||||
|
std::vector<double> squared_errors;
|
||||||
|
for (size_t index = 0; index < snapshot.camera_count; ++index)
|
||||||
|
cameras_by_image.emplace(snapshot.cameras[index].image_id,
|
||||||
|
&snapshot.cameras[index]);
|
||||||
|
for (size_t index = 0; index < snapshot.landmark_count; ++index)
|
||||||
|
landmarks_by_track.emplace(snapshot.landmarks[index].track_id,
|
||||||
|
&snapshot.landmarks[index]);
|
||||||
|
for (const auto &source : resolved.extension_observations)
|
||||||
|
if (!source_observations
|
||||||
|
.emplace(std::make_pair(source.feature_set_id,
|
||||||
|
source.feature_index),
|
||||||
|
&source)
|
||||||
|
.second)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < snapshot.component_count; ++index)
|
||||||
|
components->push_back({0, snapshot.components[index].component_key,
|
||||||
|
snapshot.components[index].registered_image_count,
|
||||||
|
snapshot.components[index].landmark_count});
|
||||||
|
for (size_t index = 0; index < snapshot.camera_count; ++index) {
|
||||||
|
Lardon3DSparseRegisteredImage camera{};
|
||||||
|
camera.image_id = snapshot.cameras[index].image_id;
|
||||||
|
camera.component_key = snapshot.cameras[index].component_key;
|
||||||
|
std::memcpy(camera.rotation_cw, snapshot.cameras[index].pose_cw.rotation_cw,
|
||||||
|
sizeof(camera.rotation_cw));
|
||||||
|
std::memcpy(camera.translation_cw,
|
||||||
|
snapshot.cameras[index].pose_cw.translation_cw,
|
||||||
|
sizeof(camera.translation_cw));
|
||||||
|
cameras->push_back(camera);
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < snapshot.landmark_count; ++index) {
|
||||||
|
const auto &source = snapshot.landmarks[index];
|
||||||
|
landmarks->push_back({0, source.track_id, source.component_key, source.point.x,
|
||||||
|
source.point.y, source.point.z,
|
||||||
|
source.reprojection_rmse_px,
|
||||||
|
source.reprojection_median_px,
|
||||||
|
source.observation_count});
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < snapshot.observation_count; ++index) {
|
||||||
|
const auto &source = snapshot.observations[index];
|
||||||
|
auto camera = cameras_by_image.find(source.image_id);
|
||||||
|
auto landmark = landmarks_by_track.find(source.track_id);
|
||||||
|
auto calibration = resolved.calibrations.find(source.image_id);
|
||||||
|
auto observed = source_observations.find(
|
||||||
|
std::make_pair(source.feature_set_id, source.feature_index));
|
||||||
|
if (camera == cameras_by_image.end() ||
|
||||||
|
landmark == landmarks_by_track.end() ||
|
||||||
|
calibration == resolved.calibrations.end() ||
|
||||||
|
observed == source_observations.end() ||
|
||||||
|
observed->second->track_id != source.track_id ||
|
||||||
|
observed->second->image_id != source.image_id)
|
||||||
|
return false;
|
||||||
|
Lardon3DSparseGeometryPoint2 pixel{observed->second->x,
|
||||||
|
observed->second->y};
|
||||||
|
double squared_error = 0.0;
|
||||||
|
if (!lardon3d_sparse_sfm_squared_reprojection_error(
|
||||||
|
&calibration->second, &camera->second->pose_cw,
|
||||||
|
&landmark->second->point, &pixel, &squared_error))
|
||||||
|
return false;
|
||||||
|
squared_errors.push_back(squared_error);
|
||||||
|
observations->push_back({0, source.track_id, source.feature_set_id,
|
||||||
|
source.feature_index, source.position_in_track});
|
||||||
|
}
|
||||||
|
*publication = {resolved.base.track_set_id, 0, 0, 0, {}, nullptr, 0,
|
||||||
|
nullptr, 0, nullptr, 0, nullptr, 0, 0.0, 0.0, 0};
|
||||||
|
publication->track_set_id = snapshot.track_set_id;
|
||||||
|
publication->calibration_scope_id = snapshot.calibration_scope_id;
|
||||||
|
publication->sfm_kind = LARDON3D_SPARSE_SFM_KIND_INCREMENTAL;
|
||||||
|
publication->sfm_version = LARDON3D_SPARSE_SFM_VERSION;
|
||||||
|
std::memcpy(publication->parameter_fingerprint, parameter_fingerprint, 32);
|
||||||
|
publication->components = components->data();
|
||||||
|
publication->component_count = components->size();
|
||||||
|
publication->registered_images = cameras->data();
|
||||||
|
publication->registered_image_count = cameras->size();
|
||||||
|
publication->landmarks = landmarks->data();
|
||||||
|
publication->landmark_count = landmarks->size();
|
||||||
|
publication->observations = observations->data();
|
||||||
|
publication->observation_count = observations->size();
|
||||||
|
if (!lardon3d_sparse_sfm_publication_metrics(
|
||||||
|
squared_errors.data(), squared_errors.size(),
|
||||||
|
&publication->reprojection_rmse_px,
|
||||||
|
&publication->reprojection_median_px))
|
||||||
|
return false;
|
||||||
|
publication->created_at = static_cast<int64_t>(std::time(nullptr));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool execute(Context *context, Lardon3DTask *task, bool *reused, bool *no_change) {
|
||||||
|
Lardon3DIncrementalReconstructionIdentity identity{
|
||||||
|
context->durable.base_reconstruction_id,
|
||||||
|
context->durable.extension_track_set_id,
|
||||||
|
context->durable.calibration_scope_id,
|
||||||
|
context->durable.incremental_kind,
|
||||||
|
context->durable.incremental_version,
|
||||||
|
{}};
|
||||||
|
std::memcpy(identity.parameter_fingerprint,
|
||||||
|
context->durable.parameter_fingerprint, 32);
|
||||||
|
unsigned char digest[32];
|
||||||
|
if (!lardon3d_incremental_reconstruction_identity_digest(&identity, digest))
|
||||||
|
return false;
|
||||||
|
Lardon3DIncrementalReconstructionMetadata existing_metadata{};
|
||||||
|
Lardon3DSparseReconstruction existing{};
|
||||||
|
auto lookup = lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
context->database, digest, &existing_metadata, &existing);
|
||||||
|
if (lookup == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
*reused = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (lookup != LARDON3D_PROJECT_DB_NOT_FOUND || !lardon3d_task_checkpoint(task))
|
||||||
|
return false;
|
||||||
|
Resolved resolved;
|
||||||
|
if (!resolve_all(context, &resolved)) return false;
|
||||||
|
Lardon3DSparseIncrementalParameters parameters{};
|
||||||
|
if (!lardon3d_sparse_incremental_parameters_default(¶meters)) return false;
|
||||||
|
Lardon3DSparseIncrementalInput extension{
|
||||||
|
context->durable.extension_track_set_id,
|
||||||
|
context->durable.calibration_scope_id,
|
||||||
|
resolved.extension_images.data(), resolved.extension_images.size(),
|
||||||
|
resolved.extension_observations.data(), resolved.extension_observations.size()};
|
||||||
|
Lardon3DIncrementalReconstructionInput input{
|
||||||
|
context->durable.base_reconstruction_id, &resolved.base,
|
||||||
|
resolved.base_track_observations.data(),
|
||||||
|
resolved.base_track_observations.size(), &extension, ¶meters,
|
||||||
|
task_checkpoint, task};
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
auto status = lardon3d_incremental_reconstruction_run(&input, &result);
|
||||||
|
if (status == LARDON3D_INCREMENTAL_RECONSTRUCTION_NO_CHANGE) {
|
||||||
|
*no_change = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (status != LARDON3D_INCREMENTAL_RECONSTRUCTION_OK) {
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::vector<Lardon3DSparseComponent> components;
|
||||||
|
std::vector<Lardon3DSparseRegisteredImage> cameras;
|
||||||
|
std::vector<Lardon3DSparseLandmark> landmarks;
|
||||||
|
std::vector<Lardon3DSparseLandmarkObservation> observations;
|
||||||
|
Lardon3DSparsePublication publication{};
|
||||||
|
if (!build_publication(resolved, result.snapshot,
|
||||||
|
context->durable.parameter_fingerprint,
|
||||||
|
&components, &cameras,
|
||||||
|
&landmarks, &observations, &publication)) {
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Lardon3DIncrementalReconstructionMetadata metadata{
|
||||||
|
0, context->durable.base_reconstruction_id,
|
||||||
|
context->durable.extension_track_set_id,
|
||||||
|
context->durable.calibration_scope_id,
|
||||||
|
context->durable.incremental_kind,
|
||||||
|
context->durable.incremental_version,
|
||||||
|
{}, {}};
|
||||||
|
std::memcpy(metadata.parameter_fingerprint,
|
||||||
|
context->durable.parameter_fingerprint, 32);
|
||||||
|
std::memcpy(metadata.scientific_identity, digest, 32);
|
||||||
|
Lardon3DSparseReconstruction published{};
|
||||||
|
auto published_status = lardon3d_incremental_reconstruction_publish(
|
||||||
|
context->database, &publication, &metadata, &published);
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
if (published_status == LARDON3D_PROJECT_DB_OK) return true;
|
||||||
|
if (published_status != LARDON3D_PROJECT_DB_CONSTRAINT) return false;
|
||||||
|
lookup = lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
context->database, digest, &existing_metadata, &existing);
|
||||||
|
*reused = lookup == LARDON3D_PROJECT_DB_OK;
|
||||||
|
return *reused;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run(Lardon3DTask *task, void *userdata) {
|
||||||
|
try {
|
||||||
|
bool reused = false;
|
||||||
|
bool no_change = false;
|
||||||
|
if (!execute(static_cast<Context *>(userdata), task, &reused, &no_change))
|
||||||
|
return lardon3d_task_fail(task,
|
||||||
|
"Reconstruction incrémentale non publiable.");
|
||||||
|
const char *message = reused ? "Reconstruction incrémentale réutilisée."
|
||||||
|
: no_change ? "Extension sans changement scientifique."
|
||||||
|
: "Reconstruction incrémentale publiée.";
|
||||||
|
return lardon3d_task_set_progress(task, 100, message);
|
||||||
|
} catch (const std::bad_alloc &) {
|
||||||
|
return lardon3d_task_fail(task,
|
||||||
|
"Mémoire insuffisante pour la phase H.");
|
||||||
|
} catch (...) {
|
||||||
|
return lardon3d_task_fail(task, "Erreur interne de la phase H.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finished(const Lardon3DTask *task, void *userdata) {
|
||||||
|
try {
|
||||||
|
(void)persist(static_cast<Context *>(userdata), task);
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_incremental_reconstruction_task_reconstruct(
|
||||||
|
const Lardon3DTaskDurableSnapshot *snapshot, void *userdata,
|
||||||
|
Lardon3DTaskKindBinding *binding) {
|
||||||
|
try {
|
||||||
|
if (!snapshot || !userdata || !binding) return false;
|
||||||
|
auto *runtime = static_cast<Lardon3DTaskReconstructionContext *>(userdata);
|
||||||
|
Lardon3DProjectDbIncrementalReconstructionTask durable{};
|
||||||
|
if (lardon3d_project_db_load_incremental_reconstruction_task(
|
||||||
|
runtime->project_db, snapshot->id, &durable) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
if (!valid_durable(durable)) return false;
|
||||||
|
auto *context = new (std::nothrow) Context;
|
||||||
|
if (!context) return false;
|
||||||
|
context->project_path = runtime->project_path;
|
||||||
|
context->database = runtime->project_db;
|
||||||
|
context->durable = durable;
|
||||||
|
if (!derive_shape(context)) {
|
||||||
|
delete context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*binding = {run, context, destroy_context, finished, context};
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" Lardon3DTask *lardon3d_project_create_incremental_reconstruction_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DIncrementalReconstructionTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id) {
|
||||||
|
try {
|
||||||
|
if (task_id) *task_id = 0;
|
||||||
|
if (!state || !state->project_loaded || !state->project_db ||
|
||||||
|
!state->resource_governor || !configuration ||
|
||||||
|
configuration->base_reconstruction_id == 0 ||
|
||||||
|
configuration->extension_track_set_id == 0 ||
|
||||||
|
configuration->calibration_scope_id == 0)
|
||||||
|
return nullptr;
|
||||||
|
auto *context = new (std::nothrow) Context;
|
||||||
|
if (!context) return nullptr;
|
||||||
|
context->project_path = state->project_path;
|
||||||
|
context->database = state->project_db;
|
||||||
|
context->durable.base_reconstruction_id =
|
||||||
|
configuration->base_reconstruction_id;
|
||||||
|
context->durable.extension_track_set_id =
|
||||||
|
configuration->extension_track_set_id;
|
||||||
|
context->durable.calibration_scope_id = configuration->calibration_scope_id;
|
||||||
|
context->durable.incremental_kind =
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_KIND;
|
||||||
|
context->durable.incremental_version =
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_VERSION;
|
||||||
|
if (!lardon3d_incremental_reconstruction_parameter_fingerprint(
|
||||||
|
context->durable.parameter_fingerprint) || !derive_shape(context)) {
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Lardon3DResourceEstimate estimate{};
|
||||||
|
if (!lardon3d_incremental_reconstruction_resource_estimate(&context->shape,
|
||||||
|
&estimate)) {
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
uint64_t id = 0;
|
||||||
|
if (lardon3d_project_db_allocate_task_id(state->project_db, &id) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK) {
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
context->durable.task_id = id;
|
||||||
|
Lardon3DTask *task = lardon3d_task_create_typed(
|
||||||
|
"Reconstruction incrémentale", &estimate,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND_VERSION, run, context,
|
||||||
|
destroy_context);
|
||||||
|
if (!task || !lardon3d_task_assign_id(task, id) ||
|
||||||
|
!lardon3d_task_set_finished_callback(task, finished, context) ||
|
||||||
|
!persist(context, task)) {
|
||||||
|
if (task)
|
||||||
|
lardon3d_task_destroy(task);
|
||||||
|
else
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (task_id) *task_id = id;
|
||||||
|
return task;
|
||||||
|
} catch (...) {
|
||||||
|
if (task_id) *task_id = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_project_enqueue_incremental_reconstruction_task(
|
||||||
|
Lardon3DAppState *state,
|
||||||
|
const Lardon3DIncrementalReconstructionTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id) {
|
||||||
|
Lardon3DTask *task = lardon3d_project_create_incremental_reconstruction_task(
|
||||||
|
state, configuration, task_id);
|
||||||
|
if (!task || !lardon3d_task_queue_add(state->task_queue, task, nullptr)) {
|
||||||
|
if (task) lardon3d_task_destroy(task);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
270
src/project_db.c
270
src/project_db.c
|
|
@ -447,6 +447,136 @@ static const char schema_sparse_sfm_task_v17[] =
|
||||||
"CHECK(length(pnp_deterministic_seed)=8),refinement_max_iterations INTEGER NOT NULL,"
|
"CHECK(length(pnp_deterministic_seed)=8),refinement_max_iterations INTEGER NOT NULL,"
|
||||||
"refinement_convergence_tolerance REAL NOT NULL);";
|
"refinement_convergence_tolerance REAL NOT NULL);";
|
||||||
|
|
||||||
|
static const char schema_sparse_derivation_identity_v18[] =
|
||||||
|
"PRAGMA legacy_alter_table=ON;"
|
||||||
|
"DROP INDEX sparse_landmark_observations_page_idx;"
|
||||||
|
"DROP INDEX sparse_landmark_track_lookup_idx;"
|
||||||
|
"DROP INDEX sparse_landmarks_page_idx;"
|
||||||
|
"DROP INDEX sparse_registered_image_lookup_idx;"
|
||||||
|
"DROP INDEX sparse_registered_page_idx;"
|
||||||
|
"DROP INDEX sparse_components_page_idx;"
|
||||||
|
"DROP INDEX sparse_reconstructions_scope_idx;"
|
||||||
|
"DROP INDEX IF EXISTS sparse_reconstructions_gate_f_candidate_idx;"
|
||||||
|
"DROP INDEX IF EXISTS sparse_reconstructions_derivation_idx;"
|
||||||
|
"ALTER TABLE sparse_landmark_observations RENAME TO sparse_landmark_observations_v17;"
|
||||||
|
"ALTER TABLE sparse_landmarks RENAME TO sparse_landmarks_v17;"
|
||||||
|
"ALTER TABLE sparse_registered_images RENAME TO sparse_registered_images_v17;"
|
||||||
|
"ALTER TABLE sparse_reconstruction_components RENAME TO sparse_reconstruction_components_v17;"
|
||||||
|
"ALTER TABLE sparse_reconstructions RENAME TO sparse_reconstructions_v17;"
|
||||||
|
"CREATE TABLE sparse_reconstructions("
|
||||||
|
"reconstruction_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(reconstruction_id>0),"
|
||||||
|
"track_set_id INTEGER NOT NULL REFERENCES track_sets(track_set_id),"
|
||||||
|
"calibration_scope_id INTEGER NOT NULL REFERENCES sparse_calibration_scopes(scope_id),"
|
||||||
|
"sfm_kind INTEGER NOT NULL CHECK(sfm_kind=1),sfm_version INTEGER NOT NULL "
|
||||||
|
"CHECK(sfm_version=1),parameter_fingerprint BLOB NOT NULL "
|
||||||
|
"CHECK(length(parameter_fingerprint)=32),derivation_identity BLOB "
|
||||||
|
"CHECK(derivation_identity IS NULL OR length(derivation_identity)=32),"
|
||||||
|
"component_count INTEGER NOT NULL CHECK(component_count>0),"
|
||||||
|
"registered_image_count INTEGER NOT NULL CHECK(registered_image_count>=2),"
|
||||||
|
"landmark_count INTEGER NOT NULL CHECK(landmark_count>0),"
|
||||||
|
"reprojection_rmse_px REAL NOT NULL,reprojection_median_px REAL NOT NULL,"
|
||||||
|
"created_at INTEGER NOT NULL);"
|
||||||
|
"INSERT INTO sparse_reconstructions("
|
||||||
|
"reconstruction_id,track_set_id,calibration_scope_id,sfm_kind,sfm_version,"
|
||||||
|
"parameter_fingerprint,derivation_identity,component_count,"
|
||||||
|
"registered_image_count,landmark_count,reprojection_rmse_px,"
|
||||||
|
"reprojection_median_px,created_at) SELECT reconstruction_id,track_set_id,"
|
||||||
|
"calibration_scope_id,sfm_kind,sfm_version,parameter_fingerprint,NULL,"
|
||||||
|
"component_count,registered_image_count,landmark_count,reprojection_rmse_px,"
|
||||||
|
"reprojection_median_px,created_at FROM sparse_reconstructions_v17;";
|
||||||
|
|
||||||
|
static const char schema_sparse_derivation_children_v18[] =
|
||||||
|
"CREATE TABLE sparse_reconstruction_components("
|
||||||
|
"component_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(component_id>0),"
|
||||||
|
"reconstruction_id INTEGER NOT NULL REFERENCES sparse_reconstructions("
|
||||||
|
"reconstruction_id) ON DELETE CASCADE,component_key INTEGER NOT NULL "
|
||||||
|
"CHECK(component_key>0),registered_image_count INTEGER NOT NULL "
|
||||||
|
"CHECK(registered_image_count>0),landmark_count INTEGER NOT NULL "
|
||||||
|
"CHECK(landmark_count>0),UNIQUE(reconstruction_id,component_id),"
|
||||||
|
"UNIQUE(reconstruction_id,component_key));"
|
||||||
|
"INSERT INTO sparse_reconstruction_components SELECT * FROM "
|
||||||
|
"sparse_reconstruction_components_v17;"
|
||||||
|
"CREATE TABLE sparse_registered_images("
|
||||||
|
"reconstruction_id INTEGER NOT NULL REFERENCES sparse_reconstructions("
|
||||||
|
"reconstruction_id) ON DELETE CASCADE,component_id INTEGER NOT NULL,"
|
||||||
|
"image_id INTEGER NOT NULL REFERENCES images(image_id),"
|
||||||
|
"rotation_00 REAL NOT NULL,rotation_01 REAL NOT NULL,rotation_02 REAL NOT NULL,"
|
||||||
|
"rotation_10 REAL NOT NULL,rotation_11 REAL NOT NULL,rotation_12 REAL NOT NULL,"
|
||||||
|
"rotation_20 REAL NOT NULL,rotation_21 REAL NOT NULL,rotation_22 REAL NOT NULL,"
|
||||||
|
"translation_0 REAL NOT NULL,translation_1 REAL NOT NULL,translation_2 REAL NOT NULL,"
|
||||||
|
"PRIMARY KEY(reconstruction_id,image_id),FOREIGN KEY(reconstruction_id,component_id) "
|
||||||
|
"REFERENCES sparse_reconstruction_components(reconstruction_id,component_id));"
|
||||||
|
"INSERT INTO sparse_registered_images SELECT * FROM sparse_registered_images_v17;"
|
||||||
|
"CREATE TABLE sparse_landmarks("
|
||||||
|
"landmark_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(landmark_id>0),"
|
||||||
|
"reconstruction_id INTEGER NOT NULL REFERENCES sparse_reconstructions("
|
||||||
|
"reconstruction_id) ON DELETE CASCADE,component_id INTEGER NOT NULL,"
|
||||||
|
"track_id INTEGER NOT NULL REFERENCES tracks(track_id),x REAL NOT NULL,y REAL NOT NULL,"
|
||||||
|
"z REAL NOT NULL,reprojection_rmse_px REAL NOT NULL,reprojection_median_px REAL NOT NULL,"
|
||||||
|
"observation_count INTEGER NOT NULL CHECK(observation_count>0),"
|
||||||
|
"UNIQUE(reconstruction_id,track_id),FOREIGN KEY(reconstruction_id,component_id) "
|
||||||
|
"REFERENCES sparse_reconstruction_components(reconstruction_id,component_id));"
|
||||||
|
"INSERT INTO sparse_landmarks SELECT * FROM sparse_landmarks_v17;"
|
||||||
|
"CREATE TABLE sparse_landmark_observations("
|
||||||
|
"landmark_id INTEGER NOT NULL REFERENCES sparse_landmarks(landmark_id) ON DELETE CASCADE,"
|
||||||
|
"feature_set_id INTEGER NOT NULL REFERENCES feature_sets(feature_set_id),"
|
||||||
|
"feature_index INTEGER NOT NULL CHECK(feature_index>=0),"
|
||||||
|
"position_in_track INTEGER NOT NULL CHECK(position_in_track>=0),"
|
||||||
|
"PRIMARY KEY(landmark_id,feature_set_id,feature_index));"
|
||||||
|
"INSERT INTO sparse_landmark_observations SELECT * FROM "
|
||||||
|
"sparse_landmark_observations_v17;"
|
||||||
|
"DROP TABLE sparse_landmark_observations_v17;"
|
||||||
|
"DROP TABLE sparse_landmarks_v17;"
|
||||||
|
"DROP TABLE sparse_registered_images_v17;"
|
||||||
|
"DROP TABLE sparse_reconstruction_components_v17;"
|
||||||
|
"DROP TABLE sparse_reconstructions_v17;"
|
||||||
|
"CREATE UNIQUE INDEX sparse_reconstructions_gate_f_candidate_idx ON "
|
||||||
|
"sparse_reconstructions(track_set_id,calibration_scope_id,sfm_kind,sfm_version,"
|
||||||
|
"parameter_fingerprint) WHERE derivation_identity IS NULL;"
|
||||||
|
"CREATE UNIQUE INDEX sparse_reconstructions_derivation_idx ON "
|
||||||
|
"sparse_reconstructions(derivation_identity) WHERE derivation_identity IS NOT NULL;"
|
||||||
|
"CREATE INDEX sparse_reconstructions_scope_idx ON "
|
||||||
|
"sparse_reconstructions(calibration_scope_id);"
|
||||||
|
"CREATE INDEX sparse_components_page_idx ON sparse_reconstruction_components("
|
||||||
|
"reconstruction_id,component_key);"
|
||||||
|
"CREATE INDEX sparse_registered_page_idx ON sparse_registered_images("
|
||||||
|
"reconstruction_id,component_id,image_id);"
|
||||||
|
"CREATE INDEX sparse_registered_image_lookup_idx ON sparse_registered_images("
|
||||||
|
"reconstruction_id,image_id);"
|
||||||
|
"CREATE INDEX sparse_landmarks_page_idx ON sparse_landmarks("
|
||||||
|
"reconstruction_id,component_id,track_id);"
|
||||||
|
"CREATE INDEX sparse_landmark_track_lookup_idx ON sparse_landmarks("
|
||||||
|
"reconstruction_id,track_id);"
|
||||||
|
"CREATE INDEX sparse_landmark_observations_page_idx ON "
|
||||||
|
"sparse_landmark_observations(landmark_id,position_in_track);"
|
||||||
|
"PRAGMA legacy_alter_table=OFF;";
|
||||||
|
|
||||||
|
static const char schema_incremental_reconstruction_v18[] =
|
||||||
|
"CREATE TABLE incremental_reconstructions("
|
||||||
|
"reconstruction_id INTEGER PRIMARY KEY REFERENCES sparse_reconstructions("
|
||||||
|
"reconstruction_id) ON DELETE CASCADE,"
|
||||||
|
"base_reconstruction_id INTEGER NOT NULL REFERENCES sparse_reconstructions("
|
||||||
|
"reconstruction_id),"
|
||||||
|
"extension_track_set_id INTEGER NOT NULL REFERENCES track_sets(track_set_id),"
|
||||||
|
"calibration_scope_id INTEGER NOT NULL REFERENCES sparse_calibration_scopes(scope_id),"
|
||||||
|
"incremental_kind INTEGER NOT NULL CHECK(incremental_kind=1),"
|
||||||
|
"incremental_version INTEGER NOT NULL CHECK(incremental_version=1),"
|
||||||
|
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32),"
|
||||||
|
"scientific_identity BLOB NOT NULL CHECK(length(scientific_identity)=32) UNIQUE,"
|
||||||
|
"UNIQUE(base_reconstruction_id,extension_track_set_id,calibration_scope_id,"
|
||||||
|
"incremental_kind,incremental_version,parameter_fingerprint));"
|
||||||
|
"CREATE INDEX incremental_reconstructions_base_idx ON incremental_reconstructions("
|
||||||
|
"base_reconstruction_id,reconstruction_id);"
|
||||||
|
"CREATE TABLE incremental_reconstruction_tasks("
|
||||||
|
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
|
||||||
|
"base_reconstruction_id INTEGER NOT NULL REFERENCES sparse_reconstructions("
|
||||||
|
"reconstruction_id),"
|
||||||
|
"extension_track_set_id INTEGER NOT NULL REFERENCES track_sets(track_set_id),"
|
||||||
|
"calibration_scope_id INTEGER NOT NULL REFERENCES sparse_calibration_scopes(scope_id),"
|
||||||
|
"incremental_kind INTEGER NOT NULL CHECK(incremental_kind=1),"
|
||||||
|
"incremental_version INTEGER NOT NULL CHECK(incremental_version=1),"
|
||||||
|
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32));";
|
||||||
|
|
||||||
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
||||||
if (destination) {
|
if (destination) {
|
||||||
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
|
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
|
||||||
|
|
@ -537,8 +667,8 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
||||||
if (from_version != 0 && from_version != 1 && from_version != 2 && from_version != 3 &&
|
if (from_version != 0 && from_version != 1 && from_version != 2 && from_version != 3 &&
|
||||||
from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 &&
|
from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 &&
|
||||||
from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 &&
|
from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 &&
|
||||||
from_version != 12 && from_version != 13 && from_version != 14 &&
|
from_version != 12 && from_version != 13 && from_version != 14 &&
|
||||||
from_version != 15) {
|
from_version != 15 && from_version != 16 && from_version != 17) {
|
||||||
return LARDON3D_PROJECT_DB_CORRUPT;
|
return LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
}
|
}
|
||||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
||||||
|
|
@ -880,6 +1010,29 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
||||||
"finish schema v17 migration");
|
"finish schema v17 migration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 18) {
|
||||||
|
result = execute(database, schema_sparse_derivation_identity_v18,
|
||||||
|
"migrate sparse derivation identity v17 to v18");
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = execute(database, schema_sparse_derivation_children_v18,
|
||||||
|
"migrate sparse derivation children v17 to v18");
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = execute(database, schema_incremental_reconstruction_v18,
|
||||||
|
"migrate incremental reconstruction v17 to v18");
|
||||||
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V18")) {
|
||||||
|
result = execute(database, "INSERT INTO missing_v18_test_table VALUES(1)",
|
||||||
|
"forced migration v18 failure");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = execute(
|
||||||
|
database,
|
||||||
|
"UPDATE metadata SET value=18 WHERE key='schema_version' AND value=17",
|
||||||
|
"finish schema v18 migration");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
result = execute(database, "COMMIT", "commit migration");
|
result = execute(database, "COMMIT", "commit migration");
|
||||||
}
|
}
|
||||||
|
|
@ -1008,7 +1161,9 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
|
||||||
"sparse_reconstruction_components",
|
"sparse_reconstruction_components",
|
||||||
"sparse_registered_images",
|
"sparse_registered_images",
|
||||||
"sparse_landmarks",
|
"sparse_landmarks",
|
||||||
"sparse_landmark_observations"};
|
"sparse_landmark_observations",
|
||||||
|
"incremental_reconstructions",
|
||||||
|
"incremental_reconstruction_tasks"};
|
||||||
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
|
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
|
||||||
result == LARDON3D_PROJECT_DB_OK;
|
result == LARDON3D_PROJECT_DB_OK;
|
||||||
++index) {
|
++index) {
|
||||||
|
|
@ -4167,6 +4322,115 @@ Lardon3DProjectDbResult lardon3d_project_db_load_sparse_sfm_task(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_record_incremental_reconstruction_task(
|
||||||
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
|
const char *kind, uint32_t version,
|
||||||
|
const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||||
|
const Lardon3DProjectDbIncrementalReconstructionTask *parameters,
|
||||||
|
int64_t updated_at) {
|
||||||
|
if (!database || !snapshot || !parameters ||
|
||||||
|
!lardon3d_task_kind_is_valid(kind) ||
|
||||||
|
strcmp(kind, "incremental_reconstruction.run") != 0 || version != 1 ||
|
||||||
|
parameters->task_id != snapshot->id ||
|
||||||
|
!valid_task_id(parameters->base_reconstruction_id) ||
|
||||||
|
!valid_task_id(parameters->extension_track_set_id) ||
|
||||||
|
!valid_task_id(parameters->calibration_scope_id) ||
|
||||||
|
parameters->incremental_kind != 1 || parameters->incremental_version != 1)
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
Lardon3DProjectDbResult result = lardon3d_project_db_record_task(
|
||||||
|
database, snapshot, kind, version, checkpoint, updated_at);
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
result = execute(database, "BEGIN IMMEDIATE", "begin incremental task payload");
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = prepare(
|
||||||
|
database,
|
||||||
|
"INSERT INTO incremental_reconstruction_tasks(task_id,base_reconstruction_id,"
|
||||||
|
"extension_track_set_id,calibration_scope_id,incremental_kind,incremental_version,"
|
||||||
|
"parameter_fingerprint) VALUES(?1,?2,?3,?4,?5,?6,?7) ON CONFLICT(task_id) DO "
|
||||||
|
"UPDATE SET task_id=excluded.task_id WHERE incremental_reconstruction_tasks."
|
||||||
|
"base_reconstruction_id=excluded.base_reconstruction_id AND "
|
||||||
|
"incremental_reconstruction_tasks.extension_track_set_id=excluded."
|
||||||
|
"extension_track_set_id AND incremental_reconstruction_tasks.calibration_scope_id="
|
||||||
|
"excluded.calibration_scope_id AND incremental_reconstruction_tasks.incremental_kind="
|
||||||
|
"excluded.incremental_kind AND incremental_reconstruction_tasks.incremental_version="
|
||||||
|
"excluded.incremental_version AND incremental_reconstruction_tasks."
|
||||||
|
"parameter_fingerprint=excluded.parameter_fingerprint",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)parameters->task_id);
|
||||||
|
sqlite3_bind_int64(statement, 2,
|
||||||
|
(sqlite3_int64)parameters->base_reconstruction_id);
|
||||||
|
sqlite3_bind_int64(statement, 3,
|
||||||
|
(sqlite3_int64)parameters->extension_track_set_id);
|
||||||
|
sqlite3_bind_int64(statement, 4,
|
||||||
|
(sqlite3_int64)parameters->calibration_scope_id);
|
||||||
|
sqlite3_bind_int(statement, 5, (int)parameters->incremental_kind);
|
||||||
|
sqlite3_bind_int(statement, 6, (int)parameters->incremental_version);
|
||||||
|
sqlite3_bind_blob(statement, 7, parameters->parameter_fingerprint, 32,
|
||||||
|
SQLITE_TRANSIENT);
|
||||||
|
result = step_done(database, statement, "upsert incremental task payload");
|
||||||
|
statement = NULL;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
||||||
|
copy_error(database->error, "Payload de reconstruction incrémentale immuable.");
|
||||||
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statement) sqlite3_finalize(statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = execute(database, "COMMIT", "commit incremental task payload");
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK)
|
||||||
|
(void)execute(database, "ROLLBACK", "rollback incremental task payload");
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_incremental_reconstruction_task(
|
||||||
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
|
Lardon3DProjectDbIncrementalReconstructionTask *parameters) {
|
||||||
|
if (!database || !valid_task_id(task_id) || !parameters)
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
memset(parameters, 0, sizeof(*parameters));
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
Lardon3DProjectDbResult result = prepare(
|
||||||
|
database,
|
||||||
|
"SELECT base_reconstruction_id,extension_track_set_id,calibration_scope_id,"
|
||||||
|
"incremental_kind,incremental_version,parameter_fingerprint FROM "
|
||||||
|
"incremental_reconstruction_tasks WHERE task_id=?1",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE)
|
||||||
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 5) != 32)
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
else {
|
||||||
|
parameters->task_id = task_id;
|
||||||
|
parameters->base_reconstruction_id =
|
||||||
|
(uint64_t)sqlite3_column_int64(statement, 0);
|
||||||
|
parameters->extension_track_set_id =
|
||||||
|
(uint64_t)sqlite3_column_int64(statement, 1);
|
||||||
|
parameters->calibration_scope_id =
|
||||||
|
(uint64_t)sqlite3_column_int64(statement, 2);
|
||||||
|
parameters->incremental_kind = (uint32_t)sqlite3_column_int(statement, 3);
|
||||||
|
parameters->incremental_version = (uint32_t)sqlite3_column_int(statement, 4);
|
||||||
|
memcpy(parameters->parameter_fingerprint, sqlite3_column_blob(statement, 5), 32);
|
||||||
|
if (!valid_task_id(parameters->base_reconstruction_id) ||
|
||||||
|
!valid_task_id(parameters->extension_track_set_id) ||
|
||||||
|
!valid_task_id(parameters->calibration_scope_id) ||
|
||||||
|
parameters->incremental_kind != 1 || parameters->incremental_version != 1)
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK) memset(parameters, 0, sizeof(*parameters));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static bool read_match_result(sqlite3_stmt *statement,
|
static bool read_match_result(sqlite3_stmt *statement,
|
||||||
Lardon3DProjectDbMatchResult *result) {
|
Lardon3DProjectDbMatchResult *result) {
|
||||||
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,37 @@ static void sparse_put_u32(unsigned char *data, size_t *offset,
|
||||||
data[(*offset)++] = (unsigned char)(value >> (byte * 8));
|
data[(*offset)++] = (unsigned char)(value >> (byte * 8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void sparse_put_u64(unsigned char *data, size_t *offset,
|
||||||
|
uint64_t value);
|
||||||
|
|
||||||
|
static bool sparse_incremental_identity_valid(
|
||||||
|
const Lardon3DIncrementalReconstructionMetadata *metadata) {
|
||||||
|
unsigned char record[76] = {0};
|
||||||
|
unsigned char digest[32] = {0};
|
||||||
|
unsigned int digest_size = 0;
|
||||||
|
size_t offset = 0;
|
||||||
|
if (!metadata || metadata->base_reconstruction_id == 0 ||
|
||||||
|
metadata->extension_track_set_id == 0 ||
|
||||||
|
metadata->calibration_scope_id == 0 || metadata->incremental_kind != 1 ||
|
||||||
|
metadata->incremental_version != 1)
|
||||||
|
return false;
|
||||||
|
memcpy(record, "L3DHIDV1", 8);
|
||||||
|
offset = 8;
|
||||||
|
sparse_put_u32(record, &offset, 1);
|
||||||
|
sparse_put_u64(record, &offset, metadata->base_reconstruction_id);
|
||||||
|
sparse_put_u64(record, &offset, metadata->extension_track_set_id);
|
||||||
|
sparse_put_u64(record, &offset, metadata->calibration_scope_id);
|
||||||
|
sparse_put_u32(record, &offset, metadata->incremental_kind);
|
||||||
|
sparse_put_u32(record, &offset, metadata->incremental_version);
|
||||||
|
memcpy(record + offset, metadata->parameter_fingerprint, 32);
|
||||||
|
offset += 32;
|
||||||
|
return offset == sizeof(record) &&
|
||||||
|
EVP_Digest(record, sizeof(record), digest, &digest_size, EVP_sha256(),
|
||||||
|
NULL) == 1 &&
|
||||||
|
digest_size == sizeof(digest) &&
|
||||||
|
memcmp(digest, metadata->scientific_identity, sizeof(digest)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void sparse_put_u64(unsigned char *data, size_t *offset,
|
static void sparse_put_u64(unsigned char *data, size_t *offset,
|
||||||
uint64_t value) {
|
uint64_t value) {
|
||||||
for (unsigned byte = 0; byte < 8; ++byte)
|
for (unsigned byte = 0; byte < 8; ++byte)
|
||||||
|
|
@ -939,7 +970,7 @@ sparse_load_reconstruction_locked(Lardon3DProjectDb *db, uint64_t id,
|
||||||
prepare(db,
|
prepare(db,
|
||||||
"SELECT "
|
"SELECT "
|
||||||
"track_set_id,calibration_scope_id,sfm_kind,sfm_version,"
|
"track_set_id,calibration_scope_id,sfm_kind,sfm_version,"
|
||||||
"parameter_fingerprint,component_count,registered_image_count,"
|
"parameter_fingerprint,derivation_identity,component_count,registered_image_count,"
|
||||||
"landmark_count,reprojection_rmse_px,reprojection_median_px FROM "
|
"landmark_count,reprojection_rmse_px,reprojection_median_px FROM "
|
||||||
"sparse_reconstructions WHERE reconstruction_id=?1",
|
"sparse_reconstructions WHERE reconstruction_id=?1",
|
||||||
&statement);
|
&statement);
|
||||||
|
|
@ -949,7 +980,9 @@ sparse_load_reconstruction_locked(Lardon3DProjectDb *db, uint64_t id,
|
||||||
int code = sqlite3_step(statement);
|
int code = sqlite3_step(statement);
|
||||||
if (code == SQLITE_DONE)
|
if (code == SQLITE_DONE)
|
||||||
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 4) != 32)
|
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 4) != 32 ||
|
||||||
|
(sqlite3_column_type(statement, 5) != SQLITE_NULL &&
|
||||||
|
sqlite3_column_bytes(statement, 5) != 32))
|
||||||
result = LARDON3D_PROJECT_DB_CORRUPT;
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
else {
|
else {
|
||||||
memset(output, 0, sizeof(*output));
|
memset(output, 0, sizeof(*output));
|
||||||
|
|
@ -960,12 +993,17 @@ sparse_load_reconstruction_locked(Lardon3DProjectDb *db, uint64_t id,
|
||||||
output->sfm_version = (uint32_t)sqlite3_column_int64(statement, 3);
|
output->sfm_version = (uint32_t)sqlite3_column_int64(statement, 3);
|
||||||
memcpy(output->parameter_fingerprint, sqlite3_column_blob(statement, 4),
|
memcpy(output->parameter_fingerprint, sqlite3_column_blob(statement, 4),
|
||||||
32);
|
32);
|
||||||
output->component_count = (uint64_t)sqlite3_column_int64(statement, 5);
|
output->has_derivation_identity =
|
||||||
|
sqlite3_column_type(statement, 5) != SQLITE_NULL;
|
||||||
|
if (output->has_derivation_identity)
|
||||||
|
memcpy(output->derivation_identity, sqlite3_column_blob(statement, 5),
|
||||||
|
32);
|
||||||
|
output->component_count = (uint64_t)sqlite3_column_int64(statement, 6);
|
||||||
output->registered_image_count =
|
output->registered_image_count =
|
||||||
(uint64_t)sqlite3_column_int64(statement, 6);
|
(uint64_t)sqlite3_column_int64(statement, 7);
|
||||||
output->landmark_count = (uint64_t)sqlite3_column_int64(statement, 7);
|
output->landmark_count = (uint64_t)sqlite3_column_int64(statement, 8);
|
||||||
output->reprojection_rmse_px = sqlite3_column_double(statement, 8);
|
output->reprojection_rmse_px = sqlite3_column_double(statement, 9);
|
||||||
output->reprojection_median_px = sqlite3_column_double(statement, 9);
|
output->reprojection_median_px = sqlite3_column_double(statement, 10);
|
||||||
if (!output->track_set_id || !output->calibration_scope_id ||
|
if (!output->track_set_id || !output->calibration_scope_id ||
|
||||||
output->sfm_kind != LARDON3D_SPARSE_SFM_KIND_INCREMENTAL ||
|
output->sfm_kind != LARDON3D_SPARSE_SFM_KIND_INCREMENTAL ||
|
||||||
output->sfm_version != LARDON3D_SPARSE_SFM_VERSION ||
|
output->sfm_version != LARDON3D_SPARSE_SFM_VERSION ||
|
||||||
|
|
@ -981,10 +1019,21 @@ sparse_load_reconstruction_locked(Lardon3DProjectDb *db, uint64_t id,
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
static Lardon3DProjectDbResult sparse_reconstruction_publish_internal(
|
||||||
Lardon3DProjectDb *db, const Lardon3DSparsePublication *publication,
|
Lardon3DProjectDb *db, const Lardon3DSparsePublication *publication,
|
||||||
|
const Lardon3DIncrementalReconstructionMetadata *metadata,
|
||||||
Lardon3DSparseReconstruction *output) {
|
Lardon3DSparseReconstruction *output) {
|
||||||
if (!db || !output || !sparse_publication_valid(publication))
|
if (!db || !output || !sparse_publication_valid(publication) ||
|
||||||
|
(metadata &&
|
||||||
|
memcmp(publication->parameter_fingerprint,
|
||||||
|
metadata->parameter_fingerprint, 32) != 0) ||
|
||||||
|
(metadata &&
|
||||||
|
(!sparse_fits_sqlite(metadata->base_reconstruction_id) ||
|
||||||
|
metadata->base_reconstruction_id == 0 ||
|
||||||
|
metadata->extension_track_set_id != publication->track_set_id ||
|
||||||
|
metadata->calibration_scope_id != publication->calibration_scope_id ||
|
||||||
|
metadata->incremental_kind != 1 || metadata->incremental_version != 1 ||
|
||||||
|
!sparse_incremental_identity_valid(metadata))))
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
(void)pthread_mutex_lock(&db->mutex);
|
(void)pthread_mutex_lock(&db->mutex);
|
||||||
Lardon3DProjectDbResult result =
|
Lardon3DProjectDbResult result =
|
||||||
|
|
@ -1022,9 +1071,9 @@ Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
||||||
db,
|
db,
|
||||||
"INSERT INTO "
|
"INSERT INTO "
|
||||||
"sparse_reconstructions(track_set_id,calibration_scope_id,sfm_kind,sfm_"
|
"sparse_reconstructions(track_set_id,calibration_scope_id,sfm_kind,sfm_"
|
||||||
"version,parameter_fingerprint,component_count,registered_image_count,"
|
"version,parameter_fingerprint,derivation_identity,component_count,registered_image_count,"
|
||||||
"landmark_count,reprojection_rmse_px,reprojection_median_px,created_at)"
|
"landmark_count,reprojection_rmse_px,reprojection_median_px,created_at)"
|
||||||
" VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11)",
|
" VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)",
|
||||||
&statement);
|
&statement);
|
||||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
sqlite3_bind_int64(statement, 1,
|
sqlite3_bind_int64(statement, 1,
|
||||||
|
|
@ -1036,20 +1085,26 @@ Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
||||||
sqlite3_bind_int64(statement, 4, publication->sfm_version);
|
sqlite3_bind_int64(statement, 4, publication->sfm_version);
|
||||||
sqlite3_bind_blob(statement, 5, publication->parameter_fingerprint, 32,
|
sqlite3_bind_blob(statement, 5, publication->parameter_fingerprint, 32,
|
||||||
SQLITE_TRANSIENT);
|
SQLITE_TRANSIENT);
|
||||||
sqlite3_bind_int64(statement, 6,
|
if (metadata)
|
||||||
(sqlite3_int64)publication->component_count);
|
sqlite3_bind_blob(statement, 6, metadata->scientific_identity, 32,
|
||||||
|
SQLITE_TRANSIENT);
|
||||||
|
else
|
||||||
|
sqlite3_bind_null(statement, 6);
|
||||||
sqlite3_bind_int64(statement, 7,
|
sqlite3_bind_int64(statement, 7,
|
||||||
(sqlite3_int64)publication->registered_image_count);
|
(sqlite3_int64)publication->component_count);
|
||||||
sqlite3_bind_int64(statement, 8,
|
sqlite3_bind_int64(statement, 8,
|
||||||
|
(sqlite3_int64)publication->registered_image_count);
|
||||||
|
sqlite3_bind_int64(statement, 9,
|
||||||
(sqlite3_int64)publication->landmark_count);
|
(sqlite3_int64)publication->landmark_count);
|
||||||
sqlite3_bind_double(statement, 9, publication->reprojection_rmse_px);
|
sqlite3_bind_double(statement, 10, publication->reprojection_rmse_px);
|
||||||
sqlite3_bind_double(statement, 10, publication->reprojection_median_px);
|
sqlite3_bind_double(statement, 11, publication->reprojection_median_px);
|
||||||
sqlite3_bind_int64(statement, 11, publication->created_at);
|
sqlite3_bind_int64(statement, 12, publication->created_at);
|
||||||
result = step_done(db, statement, "insert sparse reconstruction");
|
result = step_done(db, statement, "insert sparse reconstruction");
|
||||||
statement = NULL;
|
statement = NULL;
|
||||||
}
|
}
|
||||||
if (statement)
|
if (statement)
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
|
statement = NULL;
|
||||||
if (result == LARDON3D_PROJECT_DB_OK)
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
reconstruction_id = (uint64_t)sqlite3_last_insert_rowid(db->connection);
|
reconstruction_id = (uint64_t)sqlite3_last_insert_rowid(db->connection);
|
||||||
if (result == LARDON3D_PROJECT_DB_OK)
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
|
@ -1156,6 +1211,33 @@ Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
||||||
}
|
}
|
||||||
if (statement)
|
if (statement)
|
||||||
sqlite3_finalize(statement);
|
sqlite3_finalize(statement);
|
||||||
|
statement = NULL;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && metadata)
|
||||||
|
result = prepare(
|
||||||
|
db,
|
||||||
|
"INSERT INTO incremental_reconstructions(reconstruction_id,"
|
||||||
|
"base_reconstruction_id,extension_track_set_id,calibration_scope_id,"
|
||||||
|
"incremental_kind,incremental_version,parameter_fingerprint,"
|
||||||
|
"scientific_identity) VALUES(?1,?2,?3,?4,?5,?6,?7,?8)",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && metadata) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
||||||
|
sqlite3_bind_int64(statement, 2,
|
||||||
|
(sqlite3_int64)metadata->base_reconstruction_id);
|
||||||
|
sqlite3_bind_int64(statement, 3,
|
||||||
|
(sqlite3_int64)metadata->extension_track_set_id);
|
||||||
|
sqlite3_bind_int64(statement, 4,
|
||||||
|
(sqlite3_int64)metadata->calibration_scope_id);
|
||||||
|
sqlite3_bind_int(statement, 5, (int)metadata->incremental_kind);
|
||||||
|
sqlite3_bind_int(statement, 6, (int)metadata->incremental_version);
|
||||||
|
sqlite3_bind_blob(statement, 7, metadata->parameter_fingerprint, 32,
|
||||||
|
SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_blob(statement, 8, metadata->scientific_identity, 32,
|
||||||
|
SQLITE_TRANSIENT);
|
||||||
|
result = step_done(db, statement, "insert incremental reconstruction metadata");
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
if (statement) sqlite3_finalize(statement);
|
||||||
if (result == LARDON3D_PROJECT_DB_OK)
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
result = execute(db, "COMMIT", "commit sparse reconstruction");
|
result = execute(db, "COMMIT", "commit sparse reconstruction");
|
||||||
if (result != LARDON3D_PROJECT_DB_OK)
|
if (result != LARDON3D_PROJECT_DB_OK)
|
||||||
|
|
@ -1166,6 +1248,131 @@ Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
||||||
|
Lardon3DProjectDb *db, const Lardon3DSparsePublication *publication,
|
||||||
|
Lardon3DSparseReconstruction *output) {
|
||||||
|
return sparse_reconstruction_publish_internal(db, publication, NULL, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_publish(
|
||||||
|
Lardon3DProjectDb *db, const Lardon3DSparsePublication *publication,
|
||||||
|
const Lardon3DIncrementalReconstructionMetadata *metadata,
|
||||||
|
Lardon3DSparseReconstruction *output) {
|
||||||
|
if (!metadata) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
return sparse_reconstruction_publish_internal(db, publication, metadata, output);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Lardon3DProjectDbResult incremental_metadata_locked(
|
||||||
|
Lardon3DProjectDb *db, uint64_t reconstruction_id,
|
||||||
|
Lardon3DIncrementalReconstructionMetadata *metadata) {
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
Lardon3DProjectDbResult result = prepare(
|
||||||
|
db,
|
||||||
|
"SELECT base_reconstruction_id,extension_track_set_id,calibration_scope_id,"
|
||||||
|
"incremental_kind,incremental_version,parameter_fingerprint,scientific_identity "
|
||||||
|
"FROM incremental_reconstructions WHERE reconstruction_id=?1",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE)
|
||||||
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 5) != 32 ||
|
||||||
|
sqlite3_column_bytes(statement, 6) != 32)
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
else {
|
||||||
|
memset(metadata, 0, sizeof(*metadata));
|
||||||
|
metadata->reconstruction_id = reconstruction_id;
|
||||||
|
metadata->base_reconstruction_id =
|
||||||
|
(uint64_t)sqlite3_column_int64(statement, 0);
|
||||||
|
metadata->extension_track_set_id =
|
||||||
|
(uint64_t)sqlite3_column_int64(statement, 1);
|
||||||
|
metadata->calibration_scope_id =
|
||||||
|
(uint64_t)sqlite3_column_int64(statement, 2);
|
||||||
|
metadata->incremental_kind = (uint32_t)sqlite3_column_int(statement, 3);
|
||||||
|
metadata->incremental_version = (uint32_t)sqlite3_column_int(statement, 4);
|
||||||
|
memcpy(metadata->parameter_fingerprint, sqlite3_column_blob(statement, 5), 32);
|
||||||
|
memcpy(metadata->scientific_identity, sqlite3_column_blob(statement, 6), 32);
|
||||||
|
if (!metadata->base_reconstruction_id || !metadata->extension_track_set_id ||
|
||||||
|
!metadata->calibration_scope_id || metadata->incremental_kind != 1 ||
|
||||||
|
metadata->incremental_version != 1 ||
|
||||||
|
!sparse_incremental_identity_valid(metadata))
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK) memset(metadata, 0, sizeof(*metadata));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_load_metadata(
|
||||||
|
Lardon3DProjectDb *db, uint64_t reconstruction_id,
|
||||||
|
Lardon3DIncrementalReconstructionMetadata *metadata) {
|
||||||
|
if (!db || !reconstruction_id || !metadata)
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
(void)pthread_mutex_lock(&db->mutex);
|
||||||
|
Lardon3DProjectDbResult result =
|
||||||
|
incremental_metadata_locked(db, reconstruction_id, metadata);
|
||||||
|
Lardon3DSparseReconstruction reconstruction = {0};
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = sparse_load_reconstruction_locked(db, reconstruction_id,
|
||||||
|
&reconstruction);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
(!reconstruction.has_derivation_identity ||
|
||||||
|
memcmp(reconstruction.derivation_identity,
|
||||||
|
metadata->scientific_identity, 32) != 0 ||
|
||||||
|
memcmp(reconstruction.parameter_fingerprint,
|
||||||
|
metadata->parameter_fingerprint, 32) != 0))
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
(void)pthread_mutex_unlock(&db->mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
Lardon3DProjectDb *db, const unsigned char scientific_identity[32],
|
||||||
|
Lardon3DIncrementalReconstructionMetadata *metadata,
|
||||||
|
Lardon3DSparseReconstruction *reconstruction) {
|
||||||
|
if (!db || !scientific_identity || !metadata || !reconstruction)
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
memset(metadata, 0, sizeof(*metadata));
|
||||||
|
memset(reconstruction, 0, sizeof(*reconstruction));
|
||||||
|
(void)pthread_mutex_lock(&db->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
Lardon3DProjectDbResult result = prepare(
|
||||||
|
db,
|
||||||
|
"SELECT reconstruction_id FROM incremental_reconstructions WHERE "
|
||||||
|
"scientific_identity=?1",
|
||||||
|
&statement);
|
||||||
|
uint64_t reconstruction_id = 0;
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_blob(statement, 1, scientific_identity, 32, SQLITE_TRANSIENT);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE)
|
||||||
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
else if (code != SQLITE_ROW)
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
else
|
||||||
|
reconstruction_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = incremental_metadata_locked(db, reconstruction_id, metadata);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = sparse_load_reconstruction_locked(db, reconstruction_id, reconstruction);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
(!reconstruction->has_derivation_identity ||
|
||||||
|
memcmp(reconstruction->derivation_identity, scientific_identity, 32) != 0 ||
|
||||||
|
memcmp(reconstruction->parameter_fingerprint,
|
||||||
|
metadata->parameter_fingerprint, 32) != 0))
|
||||||
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
(void)pthread_mutex_unlock(&db->mutex);
|
||||||
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
memset(metadata, 0, sizeof(*metadata));
|
||||||
|
memset(reconstruction, 0, sizeof(*reconstruction));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_find_exact(
|
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_find_exact(
|
||||||
Lardon3DProjectDb *db, uint64_t track_set_id, uint64_t scope_id,
|
Lardon3DProjectDb *db, uint64_t track_set_id, uint64_t scope_id,
|
||||||
uint32_t kind, uint32_t version, const unsigned char fingerprint[32],
|
uint32_t kind, uint32_t version, const unsigned char fingerprint[32],
|
||||||
|
|
@ -1178,7 +1385,8 @@ Lardon3DProjectDbResult lardon3d_sparse_reconstruction_find_exact(
|
||||||
prepare(db,
|
prepare(db,
|
||||||
"SELECT reconstruction_id FROM sparse_reconstructions WHERE "
|
"SELECT reconstruction_id FROM sparse_reconstructions WHERE "
|
||||||
"track_set_id=?1 AND calibration_scope_id=?2 AND sfm_kind=?3 AND "
|
"track_set_id=?1 AND calibration_scope_id=?2 AND sfm_kind=?3 AND "
|
||||||
"sfm_version=?4 AND parameter_fingerprint=?5",
|
"sfm_version=?4 AND parameter_fingerprint=?5 AND "
|
||||||
|
"derivation_identity IS NULL",
|
||||||
&statement);
|
&statement);
|
||||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)track_set_id);
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)track_set_id);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include <lardon3d/sparse_sfm_bundle_adjustment.h>
|
#include <lardon3d/sparse_sfm_bundle_adjustment.h>
|
||||||
|
|
||||||
|
#include "incremental_reconstruction_internal.h"
|
||||||
|
|
||||||
#include <ceres/ceres.h>
|
#include <ceres/ceres.h>
|
||||||
#include <ceres/rotation.h>
|
#include <ceres/rotation.h>
|
||||||
|
|
||||||
|
|
@ -547,8 +549,10 @@ void copy_view(std::vector<T> *destination, const T *source, size_t count) {
|
||||||
if (count != 0) destination->assign(source, source + count);
|
if (count != 0) destination->assign(source, source + count);
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparationStatus prepare(const Lardon3DSparseBundleAdjustmentInput &input,
|
PreparationStatus prepare(
|
||||||
Preparation *preparation) {
|
const Lardon3DSparseBundleAdjustmentInput &input, Preparation *preparation,
|
||||||
|
const Lardon3DSparseBundleAdjustmentAnchor *anchors = nullptr,
|
||||||
|
size_t anchor_count = 0) {
|
||||||
if (!preparation || !input.incremental_result)
|
if (!preparation || !input.incremental_result)
|
||||||
return PreparationStatus::invalid_argument;
|
return PreparationStatus::invalid_argument;
|
||||||
const auto &result = *input.incremental_result;
|
const auto &result = *input.incremental_result;
|
||||||
|
|
@ -730,10 +734,45 @@ PreparationStatus prepare(const Lardon3DSparseBundleAdjustmentInput &input,
|
||||||
diagnostic.termination = LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_TERMINATION_NOT_RUN;
|
diagnostic.termination = LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_TERMINATION_NOT_RUN;
|
||||||
diagnostic.rejection_reason =
|
diagnostic.rejection_reason =
|
||||||
LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_REJECTION_INELIGIBLE;
|
LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_REJECTION_INELIGIBLE;
|
||||||
|
const Lardon3DSparseBundleAdjustmentAnchor *override = nullptr;
|
||||||
|
for (size_t anchor_index = 0; anchor_index < anchor_count; ++anchor_index) {
|
||||||
|
if (anchors[anchor_index].component_key == component.component_key) {
|
||||||
|
if (override) return PreparationStatus::invalid_argument;
|
||||||
|
override = &anchors[anchor_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool anchors_valid = false;
|
||||||
|
if (override) {
|
||||||
|
const auto pose = std::find_if(candidate.cameras.begin(), candidate.cameras.end(),
|
||||||
|
[&](const auto &camera) {
|
||||||
|
return camera.component_key == component.component_key &&
|
||||||
|
camera.image_id ==
|
||||||
|
override->pose_anchor_image_id;
|
||||||
|
});
|
||||||
|
const auto scale = std::find_if(candidate.cameras.begin(), candidate.cameras.end(),
|
||||||
|
[&](const auto &camera) {
|
||||||
|
return camera.component_key == component.component_key &&
|
||||||
|
camera.image_id ==
|
||||||
|
override->scale_anchor_image_id;
|
||||||
|
});
|
||||||
|
anchors_valid = pose != candidate.cameras.end() &&
|
||||||
|
scale != candidate.cameras.end() && pose != scale &&
|
||||||
|
override->scale_axis >=
|
||||||
|
LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_AXIS_X &&
|
||||||
|
override->scale_axis <=
|
||||||
|
LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_AXIS_Z;
|
||||||
|
if (anchors_valid) {
|
||||||
|
diagnostic.pose_anchor_image_id = override->pose_anchor_image_id;
|
||||||
|
diagnostic.scale_anchor_image_id = override->scale_anchor_image_id;
|
||||||
|
diagnostic.scale_axis = override->scale_axis;
|
||||||
|
diagnostic.has_anchors = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
anchors_valid = select_anchors(candidate.cameras, component.component_key,
|
||||||
|
&diagnostic);
|
||||||
|
}
|
||||||
diagnostic.eligible = component.registered_image_count >= 2 &&
|
diagnostic.eligible = component.registered_image_count >= 2 &&
|
||||||
component.landmark_count >= 1 &&
|
component.landmark_count >= 1 && anchors_valid;
|
||||||
select_anchors(candidate.cameras, component.component_key,
|
|
||||||
&diagnostic);
|
|
||||||
if (diagnostic.eligible)
|
if (diagnostic.eligible)
|
||||||
diagnostic.rejection_reason = LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_REJECTION_NONE;
|
diagnostic.rejection_reason = LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_REJECTION_NONE;
|
||||||
else if (!diagnostic.has_anchors && component.registered_image_count >= 2)
|
else if (!diagnostic.has_anchors && component.registered_image_count >= 2)
|
||||||
|
|
@ -1065,17 +1104,19 @@ void destroy_result(Lardon3DSparseBundleAdjustmentResult *result) {
|
||||||
|
|
||||||
} // namespace lardon3d::sparse_bundle_adjustment
|
} // namespace lardon3d::sparse_bundle_adjustment
|
||||||
|
|
||||||
extern "C" Lardon3DSparseBundleAdjustmentExecutionStatus
|
static Lardon3DSparseBundleAdjustmentExecutionStatus run_bundle_adjustment(
|
||||||
lardon3d_sparse_bundle_adjustment_run(
|
|
||||||
const Lardon3DSparseBundleAdjustmentInput *input,
|
const Lardon3DSparseBundleAdjustmentInput *input,
|
||||||
|
const Lardon3DSparseBundleAdjustmentAnchor *anchors, size_t anchor_count,
|
||||||
Lardon3DSparseBundleAdjustmentResult *result) {
|
Lardon3DSparseBundleAdjustmentResult *result) {
|
||||||
using namespace lardon3d::sparse_bundle_adjustment;
|
using namespace lardon3d::sparse_bundle_adjustment;
|
||||||
if (!result) return LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INVALID_ARGUMENT;
|
if (!result) return LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INVALID_ARGUMENT;
|
||||||
destroy_result(result);
|
destroy_result(result);
|
||||||
if (!input) return LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INVALID_ARGUMENT;
|
if (!input || (anchor_count != 0 && !anchors))
|
||||||
|
return LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INVALID_ARGUMENT;
|
||||||
try {
|
try {
|
||||||
Preparation preparation;
|
Preparation preparation;
|
||||||
const PreparationStatus preparation_status = prepare(*input, &preparation);
|
const PreparationStatus preparation_status =
|
||||||
|
prepare(*input, &preparation, anchors, anchor_count);
|
||||||
if (preparation_status == PreparationStatus::invalid_argument)
|
if (preparation_status == PreparationStatus::invalid_argument)
|
||||||
return LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INVALID_ARGUMENT;
|
return LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_INVALID_ARGUMENT;
|
||||||
if (preparation_status == PreparationStatus::out_of_memory)
|
if (preparation_status == PreparationStatus::out_of_memory)
|
||||||
|
|
@ -1145,6 +1186,21 @@ lardon3d_sparse_bundle_adjustment_run(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" Lardon3DSparseBundleAdjustmentExecutionStatus
|
||||||
|
lardon3d_sparse_bundle_adjustment_run(
|
||||||
|
const Lardon3DSparseBundleAdjustmentInput *input,
|
||||||
|
Lardon3DSparseBundleAdjustmentResult *result) {
|
||||||
|
return run_bundle_adjustment(input, nullptr, 0, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DSparseBundleAdjustmentExecutionStatus
|
||||||
|
lardon3d_sparse_bundle_adjustment_run_with_anchors(
|
||||||
|
const Lardon3DSparseBundleAdjustmentInput *input,
|
||||||
|
const Lardon3DSparseBundleAdjustmentAnchor *anchors, size_t anchor_count,
|
||||||
|
Lardon3DSparseBundleAdjustmentResult *result) {
|
||||||
|
return run_bundle_adjustment(input, anchors, anchor_count, result);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" void lardon3d_sparse_bundle_adjustment_result_destroy(
|
extern "C" void lardon3d_sparse_bundle_adjustment_result_destroy(
|
||||||
Lardon3DSparseBundleAdjustmentResult *result) {
|
Lardon3DSparseBundleAdjustmentResult *result) {
|
||||||
lardon3d::sparse_bundle_adjustment::destroy_result(result);
|
lardon3d::sparse_bundle_adjustment::destroy_result(result);
|
||||||
|
|
|
||||||
|
|
@ -150,3 +150,30 @@ bool lardon3d_sparse_sfm_publication_metrics(const double *squared_errors, size_
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool lardon3d_sparse_sfm_squared_reprojection_error(
|
||||||
|
const Lardon3DSparseGeometryCalibration *calibration,
|
||||||
|
const Lardon3DSparseGeometryPose *pose,
|
||||||
|
const Lardon3DSparseGeometryPoint3 *point,
|
||||||
|
const Lardon3DSparseGeometryPoint2 *observed, double *squared_error) {
|
||||||
|
if (!calibration || !pose || !point || !observed || !squared_error) return false;
|
||||||
|
const double *r = pose->rotation_cw;
|
||||||
|
const double *t = pose->translation_cw;
|
||||||
|
const double xc = r[0] * point->x + r[1] * point->y + r[2] * point->z + t[0];
|
||||||
|
const double yc = r[3] * point->x + r[4] * point->y + r[5] * point->z + t[1];
|
||||||
|
const double zc = r[6] * point->x + r[7] * point->y + r[8] * point->z + t[2];
|
||||||
|
if (!std::isfinite(xc) || !std::isfinite(yc) || !std::isfinite(zc) || zc <= 1e-9)
|
||||||
|
return false;
|
||||||
|
const double xn = xc / zc;
|
||||||
|
const double yn = yc / zc;
|
||||||
|
const double r2 = xn * xn + yn * yn;
|
||||||
|
const double radial = 1.0 + calibration->k1 * r2 + calibration->k2 * r2 * r2;
|
||||||
|
const double xd = xn * radial + 2.0 * calibration->p1 * xn * yn +
|
||||||
|
calibration->p2 * (r2 + 2.0 * xn * xn);
|
||||||
|
const double yd = yn * radial + calibration->p1 * (r2 + 2.0 * yn * yn) +
|
||||||
|
2.0 * calibration->p2 * xn * yn;
|
||||||
|
const double dx = calibration->fx * xd + calibration->cx - observed->x;
|
||||||
|
const double dy = calibration->fy * yd + calibration->cy - observed->y;
|
||||||
|
*squared_error = dx * dx + dy * dy;
|
||||||
|
return std::isfinite(*squared_error);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,5 +19,10 @@ bool lardon3d_sparse_sfm_component_persistable(uint64_t registered_image_count,
|
||||||
uint64_t landmark_count);
|
uint64_t landmark_count);
|
||||||
bool lardon3d_sparse_sfm_publication_metrics(const double *squared_errors, size_t count,
|
bool lardon3d_sparse_sfm_publication_metrics(const double *squared_errors, size_t count,
|
||||||
double *rmse, double *median);
|
double *rmse, double *median);
|
||||||
|
bool lardon3d_sparse_sfm_squared_reprojection_error(
|
||||||
|
const Lardon3DSparseGeometryCalibration *calibration,
|
||||||
|
const Lardon3DSparseGeometryPose *pose,
|
||||||
|
const Lardon3DSparseGeometryPoint3 *point,
|
||||||
|
const Lardon3DSparseGeometryPoint2 *observed, double *squared_error);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -389,25 +389,19 @@ bool build_publication(const Resolved &resolved,
|
||||||
observation.feature_index);
|
observation.feature_index);
|
||||||
if (camera == cameras.end() || calibration == resolved.calibrations.end() || !source)
|
if (camera == cameras.end() || calibration == resolved.calibrations.end() || !source)
|
||||||
return false;
|
return false;
|
||||||
const auto &pose = *camera->second;
|
Lardon3DSparseGeometryPose pose{};
|
||||||
const auto &point = *landmark->second;
|
std::memcpy(pose.rotation_cw, camera->second->rotation_cw,
|
||||||
double xc = pose.rotation_cw[0] * point.x + pose.rotation_cw[1] * point.y +
|
sizeof(pose.rotation_cw));
|
||||||
pose.rotation_cw[2] * point.z + pose.translation_cw[0];
|
std::memcpy(pose.translation_cw, camera->second->translation_cw,
|
||||||
double yc = pose.rotation_cw[3] * point.x + pose.rotation_cw[4] * point.y +
|
sizeof(pose.translation_cw));
|
||||||
pose.rotation_cw[5] * point.z + pose.translation_cw[1];
|
const Lardon3DSparseGeometryPoint3 point{landmark->second->x,
|
||||||
double zc = pose.rotation_cw[6] * point.x + pose.rotation_cw[7] * point.y +
|
landmark->second->y,
|
||||||
pose.rotation_cw[8] * point.z + pose.translation_cw[2];
|
landmark->second->z};
|
||||||
if (!std::isfinite(xc) || !std::isfinite(yc) || !std::isfinite(zc) || zc <= 1e-9)
|
const Lardon3DSparseGeometryPoint2 observed{source->x, source->y};
|
||||||
|
double squared = 0.0;
|
||||||
|
if (!lardon3d_sparse_sfm_squared_reprojection_error(
|
||||||
|
&calibration->second, &pose, &point, &observed, &squared))
|
||||||
return false;
|
return false;
|
||||||
double xn = xc / zc, yn = yc / zc, r2 = xn * xn + yn * yn;
|
|
||||||
const auto &cal = calibration->second;
|
|
||||||
double radial = 1.0 + cal.k1 * r2 + cal.k2 * r2 * r2;
|
|
||||||
double xd = xn * radial + 2.0 * cal.p1 * xn * yn + cal.p2 * (r2 + 2.0 * xn * xn);
|
|
||||||
double yd = yn * radial + cal.p1 * (r2 + 2.0 * yn * yn) + 2.0 * cal.p2 * xn * yn;
|
|
||||||
double dx = cal.fx * xd + cal.cx - source->x;
|
|
||||||
double dy = cal.fy * yd + cal.cy - source->y;
|
|
||||||
double squared = dx * dx + dy * dy;
|
|
||||||
if (!std::isfinite(squared)) return false;
|
|
||||||
squared_errors.push_back(squared);
|
squared_errors.push_back(squared);
|
||||||
storage->observations.push_back(
|
storage->observations.push_back(
|
||||||
{0, observation.track_id, observation.feature_set_id, observation.feature_index,
|
{0, observation.track_id, observation.feature_set_id, observation.feature_index,
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,9 @@
|
||||||
#ifdef LARDON3D_SPARSE_SFM_TASK_AVAILABLE
|
#ifdef LARDON3D_SPARSE_SFM_TASK_AVAILABLE
|
||||||
#include <lardon3d/sparse_sfm_task.h>
|
#include <lardon3d/sparse_sfm_task.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_AVAILABLE
|
||||||
|
#include <lardon3d/incremental_reconstruction_task.h>
|
||||||
|
#endif
|
||||||
#include <lardon3d/task_kind_registry.h>
|
#include <lardon3d/task_kind_registry.h>
|
||||||
|
|
||||||
const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||||
|
|
@ -68,6 +71,13 @@ const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||||
.kind_version = LARDON3D_SPARSE_SFM_TASK_KIND_VERSION,
|
.kind_version = LARDON3D_SPARSE_SFM_TASK_KIND_VERSION,
|
||||||
.reconstruct = lardon3d_sparse_sfm_task_reconstruct,
|
.reconstruct = lardon3d_sparse_sfm_task_reconstruct,
|
||||||
},
|
},
|
||||||
|
#endif
|
||||||
|
#ifdef LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_AVAILABLE
|
||||||
|
{
|
||||||
|
.kind = LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND,
|
||||||
|
.kind_version = LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND_VERSION,
|
||||||
|
.reconstruct = lardon3d_incremental_reconstruction_task_reconstruct,
|
||||||
|
},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
static const Lardon3DTaskKindRegistry registry = {
|
static const Lardon3DTaskKindRegistry registry = {
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,8 @@ static bool create_v11_database(const char *path) {
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
return execute_sql(path,
|
return execute_sql(path,
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstructions;"
|
||||||
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
|
|
@ -195,7 +197,7 @@ static bool test_model_api(const char *path) {
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(path, &database, error) ==
|
CHECK(lardon3d_project_db_open(path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
Parents parents;
|
Parents parents;
|
||||||
CHECK(create_parents(database, &parents));
|
CHECK(create_parents(database, &parents));
|
||||||
|
|
||||||
|
|
@ -479,7 +481,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v11_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v11_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
Parents parents;
|
Parents parents;
|
||||||
CHECK(create_parents(database, &parents));
|
CHECK(create_parents(database, &parents));
|
||||||
unsigned char fingerprint[32] = {0x91};
|
unsigned char fingerprint[32] = {0x91};
|
||||||
|
|
@ -494,7 +496,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
migrated_result.geometric_verification_result_id;
|
migrated_result.geometric_verification_result_id;
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 18));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v11_path,
|
query_integer(v11_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
|
||||||
|
|
@ -523,7 +525,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,8 @@ static bool run_task_test(void) {
|
||||||
CHECK(exec_sql(
|
CHECK(exec_sql(
|
||||||
database_path,
|
database_path,
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstructions;"
|
||||||
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
|
|
@ -362,7 +364,7 @@ static bool run_task_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
CHECK(remove_tree(root));
|
CHECK(remove_tree(root));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
473
tests/test_incremental_reconstruction.cpp
Normal file
473
tests/test_incremental_reconstruction.cpp
Normal file
|
|
@ -0,0 +1,473 @@
|
||||||
|
#include <lardon3d/incremental_reconstruction.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#define CHECK(value) \
|
||||||
|
do { \
|
||||||
|
if (!(value)) { \
|
||||||
|
std::fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__, \
|
||||||
|
#value); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Lardon3DSparseGeometryCalibration calibration() {
|
||||||
|
return {640, 480, 800.0, 800.0, 320.0, 240.0, 0.0, 0.0, 0.0, 0.0};
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DSparseGeometryPose pose(double center_x) {
|
||||||
|
Lardon3DSparseGeometryPose result{};
|
||||||
|
result.rotation_cw[0] = result.rotation_cw[4] = result.rotation_cw[8] = 1.0;
|
||||||
|
result.translation_cw[0] = -center_x;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DSparseGeometryPoint2 project(double center_x,
|
||||||
|
const Lardon3DSparseGeometryPoint3 &point) {
|
||||||
|
return {800.0 * (point.x - center_x) / point.z + 320.0,
|
||||||
|
800.0 * point.y / point.z + 240.0};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Fixture {
|
||||||
|
Lardon3DSparseIncrementalParameters parameters{};
|
||||||
|
std::vector<Lardon3DSparseIncrementalComponent> components;
|
||||||
|
std::vector<Lardon3DSparseIncrementalCamera> cameras;
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmark> landmarks;
|
||||||
|
std::vector<Lardon3DSparseIncrementalLandmarkObservation> base_observations;
|
||||||
|
std::vector<Lardon3DSparseIncrementalObservation> base_tracks;
|
||||||
|
std::vector<Lardon3DSparseIncrementalImage> images;
|
||||||
|
std::vector<Lardon3DSparseIncrementalObservation> extension_observations;
|
||||||
|
Lardon3DSparseIncrementalResult base{};
|
||||||
|
Lardon3DSparseIncrementalInput extension{};
|
||||||
|
|
||||||
|
void refresh() {
|
||||||
|
base.components = components.data();
|
||||||
|
base.component_count = components.size();
|
||||||
|
base.cameras = cameras.data();
|
||||||
|
base.camera_count = cameras.size();
|
||||||
|
base.landmarks = landmarks.data();
|
||||||
|
base.landmark_count = landmarks.size();
|
||||||
|
base.observations = base_observations.data();
|
||||||
|
base.observation_count = base_observations.size();
|
||||||
|
extension.images = images.data();
|
||||||
|
extension.image_count = images.size();
|
||||||
|
extension.observations = extension_observations.data();
|
||||||
|
extension.observation_count = extension_observations.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
Fixture(uint64_t new_image_id, bool descendant_new_observations,
|
||||||
|
bool new_only_observations) {
|
||||||
|
(void)lardon3d_sparse_incremental_parameters_default(¶meters);
|
||||||
|
parameters.minimum_pnp_correspondences = 6;
|
||||||
|
parameters.pnp.minimum_inliers = 6;
|
||||||
|
components.push_back({100, 2, 2, 8});
|
||||||
|
cameras.push_back({100, 100, pose(0.0)});
|
||||||
|
cameras.push_back({101, 100, pose(1.0)});
|
||||||
|
images.push_back({100, calibration()});
|
||||||
|
images.push_back({101, calibration()});
|
||||||
|
images.push_back({new_image_id, calibration()});
|
||||||
|
for (uint32_t index = 0; index < 8; ++index) {
|
||||||
|
Lardon3DSparseGeometryPoint3 point{
|
||||||
|
-0.8 + 0.25 * index, -0.35 + 0.1 * (index % 4),
|
||||||
|
4.0 + 0.3 * (index % 3)};
|
||||||
|
const uint64_t base_track = 10 + index;
|
||||||
|
const uint64_t extension_track = 1000 + index;
|
||||||
|
landmarks.push_back({500 + index, base_track, 100, point, 0.0, 0.0, 2});
|
||||||
|
for (uint32_t camera_index = 0; camera_index < 2; ++camera_index) {
|
||||||
|
const uint64_t image_id = 100 + camera_index;
|
||||||
|
const uint64_t feature_set = 10000 + image_id;
|
||||||
|
const auto pixel = project(static_cast<double>(camera_index), point);
|
||||||
|
base_tracks.push_back({base_track, image_id, feature_set, index, 64,
|
||||||
|
pixel.x, pixel.y});
|
||||||
|
extension_observations.push_back(
|
||||||
|
{extension_track, image_id, feature_set, index, 64, pixel.x, pixel.y});
|
||||||
|
base_observations.push_back({500 + index, base_track, image_id,
|
||||||
|
feature_set, index, camera_index});
|
||||||
|
}
|
||||||
|
if (descendant_new_observations) {
|
||||||
|
const auto pixel = project(0.5, point);
|
||||||
|
extension_observations.push_back(
|
||||||
|
{extension_track, new_image_id, 10000 + new_image_id, index, 64,
|
||||||
|
pixel.x, pixel.y});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (new_only_observations)
|
||||||
|
extension_observations.push_back(
|
||||||
|
{9000, new_image_id, 20000 + new_image_id, 0, 1, 320.0, 240.0});
|
||||||
|
base.status = LARDON3D_SPARSE_INCREMENTAL_COMPLETE;
|
||||||
|
base.track_set_id = 1;
|
||||||
|
base.calibration_scope_id = 1;
|
||||||
|
extension = {2, 2, images.data(), images.size(), extension_observations.data(),
|
||||||
|
extension_observations.size()};
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DIncrementalReconstructionStatus run(
|
||||||
|
Lardon3DIncrementalReconstructionResult *result) {
|
||||||
|
Lardon3DIncrementalReconstructionInput input{
|
||||||
|
7, &base, base_tracks.data(), base_tracks.size(), &extension,
|
||||||
|
¶meters, nullptr, nullptr};
|
||||||
|
return lardon3d_incremental_reconstruction_run(&input, result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool test_identity_and_estimate() {
|
||||||
|
unsigned char fingerprint[32]{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_parameter_fingerprint(fingerprint));
|
||||||
|
const unsigned char golden[32] = {
|
||||||
|
0xf4, 0x4a, 0x89, 0xb2, 0x3b, 0x52, 0x04, 0x81,
|
||||||
|
0x70, 0x18, 0x48, 0xec, 0xf1, 0x75, 0x63, 0x8e,
|
||||||
|
0x82, 0xf2, 0xc9, 0xe2, 0x5b, 0x70, 0xa0, 0x1f,
|
||||||
|
0x3e, 0xb7, 0x67, 0xbf, 0x28, 0x44, 0x6c, 0xd8};
|
||||||
|
CHECK(std::memcmp(fingerprint, golden, 32) == 0);
|
||||||
|
const unsigned char zero[32]{};
|
||||||
|
CHECK(std::memcmp(fingerprint, zero, 32) != 0);
|
||||||
|
Lardon3DIncrementalReconstructionIdentity identity{7, 8, 9, 1, 1, {}};
|
||||||
|
std::memcpy(identity.parameter_fingerprint, fingerprint, 32);
|
||||||
|
unsigned char first[32]{};
|
||||||
|
unsigned char second[32]{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(&identity, first));
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(&identity, second));
|
||||||
|
CHECK(std::memcmp(first, second, 32) == 0);
|
||||||
|
++identity.base_reconstruction_id;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(&identity, second));
|
||||||
|
CHECK(std::memcmp(first, second, 32) != 0);
|
||||||
|
--identity.base_reconstruction_id;
|
||||||
|
++identity.extension_track_set_id;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(&identity, second));
|
||||||
|
CHECK(std::memcmp(first, second, 32) != 0);
|
||||||
|
--identity.extension_track_set_id;
|
||||||
|
++identity.calibration_scope_id;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(&identity, second));
|
||||||
|
CHECK(std::memcmp(first, second, 32) != 0);
|
||||||
|
|
||||||
|
Lardon3DIncrementalReconstructionShape shape{2, 8, 16, 3, 8, 24};
|
||||||
|
Lardon3DResourceEstimate estimate{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_resource_estimate(&shape, &estimate));
|
||||||
|
CHECK(estimate.memory_fixed_bytes == 269484032);
|
||||||
|
CHECK(estimate.minimum_batch_size == 1 && estimate.maximum_batch_size == 1);
|
||||||
|
CHECK(estimate.desired_cpu_threads == 1 && estimate.desired_gpu_slots == 0 &&
|
||||||
|
estimate.desired_io_slots == 1 &&
|
||||||
|
estimate.task_class == LARDON3D_RESOURCE_TASK_CPU);
|
||||||
|
shape.base_camera_count = std::numeric_limits<uint64_t>::max();
|
||||||
|
CHECK(!lardon3d_incremental_reconstruction_resource_estimate(&shape, &estimate));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_lineage_failures() {
|
||||||
|
Fixture missing(150, false, false);
|
||||||
|
missing.extension_observations.erase(missing.extension_observations.begin());
|
||||||
|
missing.extension.observations = missing.extension_observations.data();
|
||||||
|
missing.extension.observation_count = missing.extension_observations.size();
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(missing.run(&result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MISSING);
|
||||||
|
|
||||||
|
Fixture split(150, false, false);
|
||||||
|
split.extension_observations[1].track_id = 7000;
|
||||||
|
CHECK(split.run(&result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_SPLIT);
|
||||||
|
|
||||||
|
Fixture merge(150, false, false);
|
||||||
|
for (auto &observation : merge.extension_observations)
|
||||||
|
if (observation.track_id == 1001) observation.track_id = 1000;
|
||||||
|
CHECK(merge.run(&result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_MERGE);
|
||||||
|
|
||||||
|
Fixture duplicate(150, false, false);
|
||||||
|
duplicate.extension_observations.push_back(duplicate.extension_observations.front());
|
||||||
|
duplicate.extension.observations = duplicate.extension_observations.data();
|
||||||
|
duplicate.extension.observation_count = duplicate.extension_observations.size();
|
||||||
|
CHECK(duplicate.run(&result) ==
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_LINEAGE_DUPLICATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_h_b01() {
|
||||||
|
Fixture fixture(150, true, false);
|
||||||
|
fixture.parameters.maximum_registration_rounds = 0;
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(fixture.run(&result) ==
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_DESCENDANT_UNREGISTERED_OBSERVATION);
|
||||||
|
|
||||||
|
Fixture new_only(150, false, true);
|
||||||
|
new_only.parameters.maximum_registration_rounds = 0;
|
||||||
|
CHECK(new_only.run(&result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_NO_CHANGE);
|
||||||
|
CHECK(!result.changed && result.snapshot.component_count == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_h_b02() {
|
||||||
|
Fixture fixture(99, true, false);
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(fixture.run(&result) ==
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_COMPONENT_KEY_VIOLATION);
|
||||||
|
CHECK(!result.changed && result.snapshot.component_count == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reject_checkpoint(void *) { return false; }
|
||||||
|
|
||||||
|
bool test_cancellation() {
|
||||||
|
Fixture fixture(150, true, false);
|
||||||
|
Lardon3DIncrementalReconstructionInput input{
|
||||||
|
7, &fixture.base, fixture.base_tracks.data(), fixture.base_tracks.size(),
|
||||||
|
&fixture.extension, &fixture.parameters, reject_checkpoint, nullptr};
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_run(&input, &result) ==
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_CANCELLED);
|
||||||
|
CHECK(!result.changed && result.snapshot.component_count == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_successful_enrichment() {
|
||||||
|
Fixture fixture(150, true, false);
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(fixture.run(&result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_OK);
|
||||||
|
CHECK(result.changed && result.snapshot.component_count == 1 &&
|
||||||
|
result.snapshot.camera_count == 3 && result.snapshot.landmark_count == 8 &&
|
||||||
|
result.snapshot.observation_count == 24);
|
||||||
|
CHECK(result.snapshot.components[0].component_key == 100);
|
||||||
|
CHECK(result.snapshot.cameras[0].image_id == 100 &&
|
||||||
|
result.snapshot.cameras[2].image_id == 150);
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_second_component(Fixture *fixture) {
|
||||||
|
fixture->components.push_back({200, 2, 2, 8});
|
||||||
|
fixture->cameras.push_back({200, 200, pose(10.0)});
|
||||||
|
fixture->cameras.push_back({201, 200, pose(11.0)});
|
||||||
|
fixture->images.push_back({200, calibration()});
|
||||||
|
fixture->images.push_back({201, calibration()});
|
||||||
|
for (uint32_t index = 0; index < 8; ++index) {
|
||||||
|
Lardon3DSparseGeometryPoint3 point{
|
||||||
|
9.2 + 0.25 * index, -0.35 + 0.1 * (index % 4),
|
||||||
|
4.0 + 0.3 * (index % 3)};
|
||||||
|
const uint64_t base_track = 30 + index;
|
||||||
|
const uint64_t extension_track = 3000 + index;
|
||||||
|
fixture->landmarks.push_back(
|
||||||
|
{700 + index, base_track, 200, point, 0.0, 0.0, 2});
|
||||||
|
for (uint32_t camera_index = 0; camera_index < 2; ++camera_index) {
|
||||||
|
const uint64_t image_id = 200 + camera_index;
|
||||||
|
const uint64_t feature_set = 10000 + image_id;
|
||||||
|
const auto pixel = project(10.0 + camera_index, point);
|
||||||
|
fixture->base_tracks.push_back({base_track, image_id, feature_set, index,
|
||||||
|
64, pixel.x, pixel.y});
|
||||||
|
fixture->extension_observations.push_back(
|
||||||
|
{extension_track, image_id, feature_set, index, 64, pixel.x, pixel.y});
|
||||||
|
fixture->base_observations.push_back(
|
||||||
|
{700 + index, base_track, image_id, feature_set, index, camera_index});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fixture->refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_component_isolation_and_bridge() {
|
||||||
|
Fixture fixture(150, true, false);
|
||||||
|
add_second_component(&fixture);
|
||||||
|
const auto camera_b0 = fixture.cameras[2];
|
||||||
|
const auto landmark_b0 = fixture.landmarks[8];
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(fixture.run(&result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_OK);
|
||||||
|
CHECK(result.snapshot.component_count == 2 &&
|
||||||
|
result.snapshot.components[0].component_key == 100 &&
|
||||||
|
result.snapshot.components[1].component_key == 200);
|
||||||
|
auto camera = std::find_if(result.snapshot.cameras,
|
||||||
|
result.snapshot.cameras + result.snapshot.camera_count,
|
||||||
|
[](const auto &item) { return item.image_id == 200; });
|
||||||
|
auto landmark = std::find_if(
|
||||||
|
result.snapshot.landmarks,
|
||||||
|
result.snapshot.landmarks + result.snapshot.landmark_count,
|
||||||
|
[](const auto &item) { return item.track_id == 3000; });
|
||||||
|
CHECK(camera != result.snapshot.cameras + result.snapshot.camera_count &&
|
||||||
|
landmark != result.snapshot.landmarks + result.snapshot.landmark_count);
|
||||||
|
CHECK(std::memcmp(&camera->pose_cw, &camera_b0.pose_cw,
|
||||||
|
sizeof(camera->pose_cw)) == 0);
|
||||||
|
CHECK(std::memcmp(&landmark->point, &landmark_b0.point,
|
||||||
|
sizeof(landmark->point)) == 0);
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
|
||||||
|
Fixture bridge(150, true, false);
|
||||||
|
add_second_component(&bridge);
|
||||||
|
for (auto &observation : bridge.extension_observations)
|
||||||
|
if (observation.track_id == 3000) observation.track_id = 1000;
|
||||||
|
bridge.refresh();
|
||||||
|
CHECK(bridge.run(&result) ==
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_CROSS_COMPONENT_BRIDGE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool expect_invalid_base(Fixture *fixture) {
|
||||||
|
fixture->refresh();
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(fixture->run(&result) ==
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE);
|
||||||
|
CHECK(!result.changed && result.snapshot.components == nullptr &&
|
||||||
|
result.snapshot.component_count == 0);
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_base_observation_validation() {
|
||||||
|
Fixture wrong_landmark(150, false, false);
|
||||||
|
++wrong_landmark.base_observations[0].landmark_id;
|
||||||
|
CHECK(expect_invalid_base(&wrong_landmark));
|
||||||
|
|
||||||
|
Fixture wrong_track(150, false, false);
|
||||||
|
++wrong_track.base_observations[0].track_id;
|
||||||
|
CHECK(expect_invalid_base(&wrong_track));
|
||||||
|
|
||||||
|
Fixture duplicate_position(150, false, false);
|
||||||
|
duplicate_position.base_observations[1].position_in_track = 0;
|
||||||
|
CHECK(expect_invalid_base(&duplicate_position));
|
||||||
|
|
||||||
|
Fixture invalid_position(150, false, false);
|
||||||
|
invalid_position.base_observations[0].position_in_track = 2;
|
||||||
|
CHECK(expect_invalid_base(&invalid_position));
|
||||||
|
|
||||||
|
Fixture wrong_count(150, false, false);
|
||||||
|
++wrong_count.landmarks[0].observation_count;
|
||||||
|
CHECK(expect_invalid_base(&wrong_count));
|
||||||
|
|
||||||
|
Fixture cross_component(150, false, false);
|
||||||
|
add_second_component(&cross_component);
|
||||||
|
cross_component.base_observations[0].image_id = 200;
|
||||||
|
CHECK(expect_invalid_base(&cross_component));
|
||||||
|
|
||||||
|
Fixture missing_source(150, false, false);
|
||||||
|
missing_source.base_tracks.pop_back();
|
||||||
|
CHECK(expect_invalid_base(&missing_source));
|
||||||
|
|
||||||
|
Fixture extra_source(150, false, false);
|
||||||
|
extra_source.base_tracks.push_back(extra_source.base_tracks.front());
|
||||||
|
extra_source.base_tracks.back().feature_index = 63;
|
||||||
|
CHECK(expect_invalid_base(&extra_source));
|
||||||
|
|
||||||
|
Fixture wrong_source_image(150, false, false);
|
||||||
|
wrong_source_image.base_tracks[0].image_id = 101;
|
||||||
|
CHECK(expect_invalid_base(&wrong_source_image));
|
||||||
|
|
||||||
|
Fixture wrong_source_feature(150, false, false);
|
||||||
|
++wrong_source_feature.base_tracks[0].feature_index;
|
||||||
|
CHECK(expect_invalid_base(&wrong_source_feature));
|
||||||
|
|
||||||
|
Fixture duplicate_source(150, false, false);
|
||||||
|
duplicate_source.base_tracks.push_back(duplicate_source.base_tracks.front());
|
||||||
|
CHECK(expect_invalid_base(&duplicate_source));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_landmark_id_exhaustion() {
|
||||||
|
Fixture fixture(150, true, false);
|
||||||
|
fixture.images.push_back({151, calibration()});
|
||||||
|
for (uint32_t index = 0; index < 8; ++index) {
|
||||||
|
const auto pixel = project(0.75, fixture.landmarks[index].point);
|
||||||
|
fixture.extension_observations.push_back(
|
||||||
|
{1000 + index, 151, 10151, index, 64, pixel.x, pixel.y});
|
||||||
|
}
|
||||||
|
const Lardon3DSparseGeometryPoint3 new_point{0.2, -0.1, 5.5};
|
||||||
|
const auto first = project(0.5, new_point);
|
||||||
|
const auto second = project(0.75, new_point);
|
||||||
|
fixture.extension_observations.push_back(
|
||||||
|
{9000, 150, 20150, 0, 1, first.x, first.y});
|
||||||
|
fixture.extension_observations.push_back(
|
||||||
|
{9000, 151, 20151, 0, 1, second.x, second.y});
|
||||||
|
fixture.landmarks.back().landmark_id = UINT64_MAX - 1;
|
||||||
|
for (auto &observation : fixture.base_observations)
|
||||||
|
if (observation.track_id == fixture.landmarks.back().track_id)
|
||||||
|
observation.landmark_id = UINT64_MAX - 1;
|
||||||
|
fixture.refresh();
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(fixture.run(&result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_INVALID_BASE);
|
||||||
|
CHECK(!result.changed && result.snapshot.components == nullptr);
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool snapshots_equal(const Lardon3DSparseIncrementalResult &left,
|
||||||
|
const Lardon3DSparseIncrementalResult &right) {
|
||||||
|
return left.component_count == right.component_count &&
|
||||||
|
left.camera_count == right.camera_count &&
|
||||||
|
left.landmark_count == right.landmark_count &&
|
||||||
|
left.observation_count == right.observation_count &&
|
||||||
|
std::memcmp(left.components, right.components,
|
||||||
|
left.component_count * sizeof(*left.components)) == 0 &&
|
||||||
|
std::memcmp(left.cameras, right.cameras,
|
||||||
|
left.camera_count * sizeof(*left.cameras)) == 0 &&
|
||||||
|
std::memcmp(left.landmarks, right.landmarks,
|
||||||
|
left.landmark_count * sizeof(*left.landmarks)) == 0 &&
|
||||||
|
std::memcmp(left.observations, right.observations,
|
||||||
|
left.observation_count * sizeof(*left.observations)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_new_only_triangulation_and_determinism() {
|
||||||
|
Fixture fixture(150, true, false);
|
||||||
|
fixture.images.push_back({151, calibration()});
|
||||||
|
for (uint32_t index = 0; index < 8; ++index) {
|
||||||
|
const auto pixel = project(0.75, fixture.landmarks[index].point);
|
||||||
|
fixture.extension_observations.push_back(
|
||||||
|
{1000 + index, 151, 10151, index, 64, pixel.x, pixel.y});
|
||||||
|
}
|
||||||
|
const Lardon3DSparseGeometryPoint3 new_point{0.2, -0.1, 5.5};
|
||||||
|
auto first = project(0.5, new_point);
|
||||||
|
auto second = project(0.75, new_point);
|
||||||
|
fixture.extension_observations.push_back(
|
||||||
|
{9000, 150, 20150, 0, 1, first.x, first.y});
|
||||||
|
fixture.extension_observations.push_back(
|
||||||
|
{9000, 151, 20151, 0, 1, second.x, second.y});
|
||||||
|
fixture.refresh();
|
||||||
|
|
||||||
|
Lardon3DIncrementalReconstructionResult first_result{};
|
||||||
|
Lardon3DIncrementalReconstructionResult second_result{};
|
||||||
|
CHECK(fixture.run(&first_result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_OK);
|
||||||
|
CHECK(fixture.run(&second_result) == LARDON3D_INCREMENTAL_RECONSTRUCTION_OK);
|
||||||
|
CHECK(first_result.snapshot.camera_count == 4 &&
|
||||||
|
first_result.snapshot.landmark_count == 9 &&
|
||||||
|
first_result.snapshot.components[0].component_key == 100);
|
||||||
|
auto landmark = std::lower_bound(
|
||||||
|
first_result.snapshot.landmarks,
|
||||||
|
first_result.snapshot.landmarks + first_result.snapshot.landmark_count,
|
||||||
|
UINT64_C(9000), [](const auto &item, uint64_t track_id) {
|
||||||
|
return item.track_id < track_id;
|
||||||
|
});
|
||||||
|
CHECK(landmark != first_result.snapshot.landmarks +
|
||||||
|
first_result.snapshot.landmark_count &&
|
||||||
|
landmark->track_id == 9000 && landmark->component_key == 100 &&
|
||||||
|
landmark->observation_count == 2);
|
||||||
|
CHECK(snapshots_equal(first_result.snapshot, second_result.snapshot));
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&first_result);
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&second_result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_bundle_adjustment_failure() {
|
||||||
|
Fixture fixture(150, true, false);
|
||||||
|
fixture.cameras[1].pose_cw = fixture.cameras[0].pose_cw;
|
||||||
|
fixture.refresh();
|
||||||
|
Lardon3DIncrementalReconstructionResult result{};
|
||||||
|
CHECK(fixture.run(&result) ==
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_BUNDLE_ADJUSTMENT_FAILED);
|
||||||
|
CHECK(!result.changed && result.snapshot.components == nullptr &&
|
||||||
|
result.snapshot.component_count == 0);
|
||||||
|
lardon3d_incremental_reconstruction_result_destroy(&result);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
if (!test_identity_and_estimate() || !test_lineage_failures() ||
|
||||||
|
!test_h_b01() || !test_h_b02() || !test_cancellation() ||
|
||||||
|
!test_successful_enrichment() ||
|
||||||
|
!test_component_isolation_and_bridge() ||
|
||||||
|
!test_base_observation_validation() ||
|
||||||
|
!test_landmark_id_exhaustion() ||
|
||||||
|
!test_new_only_triangulation_and_determinism() ||
|
||||||
|
!test_bundle_adjustment_failure())
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -53,6 +53,8 @@ static bool create_v9_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstructions;"
|
||||||
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
|
|
@ -117,7 +119,7 @@ static bool run_test(void) {
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 17);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 18);
|
||||||
|
|
||||||
Lardon3DProjectDbScanSet scanset;
|
Lardon3DProjectDbScanSet scanset;
|
||||||
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
||||||
|
|
@ -494,7 +496,7 @@ static bool run_test(void) {
|
||||||
|
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
|
|
||||||
/* Verify persistence: load previously created results */
|
/* Verify persistence: load previously created results */
|
||||||
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
|
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
|
||||||
|
|
@ -537,11 +539,11 @@ static bool run_test(void) {
|
||||||
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
||||||
CHECK(lardon3d_project_db_open(v9_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v9_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 18));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v9_path,
|
query_integer(v9_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
@ -569,7 +571,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,8 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
|
||||||
}
|
}
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE IF EXISTS incremental_reconstructions;"
|
||||||
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
|
|
@ -366,7 +368,7 @@ static bool run_test(void) {
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
0));
|
0));
|
||||||
CHECK(reopen_runtime(&fixture));
|
CHECK(reopen_runtime(&fixture));
|
||||||
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 17);
|
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 18);
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(database_path,
|
query_integer(database_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ static bool create_future_database(const char *path) {
|
||||||
sqlite3_exec(
|
sqlite3_exec(
|
||||||
connection,
|
connection,
|
||||||
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||||
"INSERT INTO metadata VALUES('schema_version',18);",
|
"INSERT INTO metadata VALUES('schema_version',19);",
|
||||||
NULL, NULL, NULL) == SQLITE_OK;
|
NULL, NULL, NULL) == SQLITE_OK;
|
||||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +133,8 @@ static bool create_v7_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE incremental_reconstructions;"
|
||||||
"DROP TABLE sparse_sfm_tasks;"
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
|
|
@ -170,6 +172,8 @@ static bool create_v6_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE incremental_reconstructions;"
|
||||||
"DROP TABLE sparse_sfm_tasks;"
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
|
|
@ -215,6 +219,8 @@ static bool create_v10_database(const char *path) {
|
||||||
}
|
}
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE incremental_reconstructions;"
|
||||||
"DROP TABLE sparse_sfm_tasks;"
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
|
|
@ -249,6 +255,8 @@ static bool create_v13_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE incremental_reconstructions;"
|
||||||
"DROP TABLE sparse_sfm_tasks;"
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
|
|
@ -870,7 +878,8 @@ static bool run_test(void) {
|
||||||
char direct_v5_path[512], v8_path[512], failed_v8_path[512];
|
char direct_v5_path[512], v8_path[512], failed_v8_path[512];
|
||||||
char v10_path[512], failed_v11_path[512];
|
char v10_path[512], failed_v11_path[512];
|
||||||
char v13_path[512], true_v14_path[512], failed_v14_path[512],
|
char v13_path[512], true_v14_path[512], failed_v14_path[512],
|
||||||
failed_v15_path[512], true_v15_path[512];
|
failed_v15_path[512], true_v15_path[512], v17_path[512],
|
||||||
|
failed_v18_path[512];
|
||||||
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db",
|
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db",
|
||||||
directory) > 0);
|
directory) > 0);
|
||||||
CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin",
|
CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin",
|
||||||
|
|
@ -913,12 +922,15 @@ static bool run_test(void) {
|
||||||
directory) > 0);
|
directory) > 0);
|
||||||
CHECK(snprintf(true_v15_path, sizeof(true_v15_path), "%s/true-v15.db",
|
CHECK(snprintf(true_v15_path, sizeof(true_v15_path), "%s/true-v15.db",
|
||||||
directory) > 0);
|
directory) > 0);
|
||||||
|
CHECK(snprintf(v17_path, sizeof(v17_path), "%s/v17.db", directory) > 0);
|
||||||
|
CHECK(snprintf(failed_v18_path, sizeof(failed_v18_path),
|
||||||
|
"%s/failed-v18.db", directory) > 0);
|
||||||
|
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 17);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 18);
|
||||||
bool legacy_pending = true;
|
bool legacy_pending = true;
|
||||||
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
|
@ -1236,9 +1248,69 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(database_path,
|
CHECK(query_integer(database_path,
|
||||||
"SELECT value FROM metadata WHERE key='schema_version'",
|
"SELECT value FROM metadata WHERE key='schema_version'",
|
||||||
17));
|
18));
|
||||||
CHECK(query_integer(database_path,
|
CHECK(query_integer(database_path,
|
||||||
"SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
"SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
||||||
|
|
||||||
|
CHECK(lardon3d_project_db_open(v17_path, &database, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
database = NULL;
|
||||||
|
CHECK(execute_test_sql(
|
||||||
|
v17_path,
|
||||||
|
"INSERT INTO track_sets(track_set_id,builder_kind,builder_version,"
|
||||||
|
"parameter_fingerprint,verifier_kind,verifier_version,verifier_fingerprint,"
|
||||||
|
"input_scope_hash,gvr_count,track_count,created_at) VALUES(1,'builder',1,"
|
||||||
|
"zeroblob(32),1,1,zeroblob(32),zeroblob(32),1,0,1);"
|
||||||
|
"INSERT INTO sparse_calibration_scopes(scope_id,scientific_hash,member_count) "
|
||||||
|
"VALUES(1,randomblob(32),1);"
|
||||||
|
"INSERT INTO sparse_reconstructions(reconstruction_id,track_set_id,"
|
||||||
|
"calibration_scope_id,sfm_kind,sfm_version,parameter_fingerprint,component_count,"
|
||||||
|
"registered_image_count,landmark_count,reprojection_rmse_px,reprojection_median_px,"
|
||||||
|
"created_at) VALUES(1,1,1,1,1,zeroblob(32),1,2,1,0,0,1);"
|
||||||
|
"DROP TABLE incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE incremental_reconstructions;"
|
||||||
|
"UPDATE metadata SET value=17 WHERE key='schema_version';"));
|
||||||
|
CHECK(lardon3d_project_db_open(v17_path, &database, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
database = NULL;
|
||||||
|
CHECK(query_integer(v17_path,
|
||||||
|
"SELECT count(*) FROM sparse_reconstructions WHERE "
|
||||||
|
"reconstruction_id=1 AND derivation_identity IS NULL "
|
||||||
|
"AND parameter_fingerprint=zeroblob(32)", 1));
|
||||||
|
CHECK(query_integer(v17_path, "SELECT count(*) FROM pragma_foreign_key_check", 0));
|
||||||
|
CHECK(query_integer(v17_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
"name IN('incremental_reconstructions',"
|
||||||
|
"'incremental_reconstruction_tasks')", 2));
|
||||||
|
|
||||||
|
CHECK(lardon3d_project_db_open(failed_v18_path, &database, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
database = NULL;
|
||||||
|
CHECK(execute_test_sql(
|
||||||
|
failed_v18_path,
|
||||||
|
"DROP TABLE incremental_reconstruction_tasks;"
|
||||||
|
"DROP TABLE incremental_reconstructions;"
|
||||||
|
"UPDATE metadata SET value=17 WHERE key='schema_version';"));
|
||||||
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V18", "1", 1) == 0);
|
||||||
|
CHECK(lardon3d_project_db_open(failed_v18_path, &database, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_IO_ERROR);
|
||||||
|
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V18") == 0);
|
||||||
|
CHECK(query_integer(failed_v18_path,
|
||||||
|
"SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
|
CHECK(query_integer(failed_v18_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
"name='incremental_reconstructions'", 0));
|
||||||
|
CHECK(query_integer(failed_v18_path,
|
||||||
|
"SELECT count(*) FROM pragma_foreign_key_check", 0));
|
||||||
|
CHECK(lardon3d_project_db_open(failed_v18_path, &database, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
database = NULL;
|
||||||
|
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 1, &task) ==
|
CHECK(lardon3d_project_db_load_task(database, 1, &task) ==
|
||||||
|
|
@ -1264,7 +1336,7 @@ static bool run_test(void) {
|
||||||
CHECK(create_v1_database(legacy_path));
|
CHECK(create_v1_database(legacy_path));
|
||||||
CHECK(lardon3d_project_db_open(legacy_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(legacy_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
CHECK(lardon3d_project_db_get_project(database, &loaded_project) ==
|
CHECK(lardon3d_project_db_get_project(database, &loaded_project) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
||||||
|
|
@ -1279,7 +1351,7 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(legacy_path,
|
CHECK(query_integer(legacy_path,
|
||||||
"SELECT value FROM metadata WHERE key='schema_version'",
|
"SELECT value FROM metadata WHERE key='schema_version'",
|
||||||
17));
|
18));
|
||||||
|
|
||||||
CHECK(create_v1_database(failed_migration_path));
|
CHECK(create_v1_database(failed_migration_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
||||||
|
|
@ -1310,7 +1382,7 @@ static bool run_test(void) {
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 18));
|
||||||
|
|
||||||
CHECK(create_v2_database(failed_v3_migration_path));
|
CHECK(create_v2_database(failed_v3_migration_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
|
||||||
|
|
@ -1344,7 +1416,7 @@ static bool run_test(void) {
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 18));
|
||||||
|
|
||||||
CHECK(create_v3_database(failed_v4_path));
|
CHECK(create_v3_database(failed_v4_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
|
||||||
|
|
@ -1366,7 +1438,7 @@ static bool run_test(void) {
|
||||||
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
|
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
|
||||||
}
|
}
|
||||||
CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
|
CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) ==
|
CHECK(lardon3d_project_db_load_task(database, 9, &task) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact",
|
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact",
|
||||||
|
|
@ -1421,7 +1493,7 @@ static bool run_test(void) {
|
||||||
error);
|
error);
|
||||||
}
|
}
|
||||||
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1431,7 +1503,7 @@ static bool run_test(void) {
|
||||||
5));
|
5));
|
||||||
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1440,11 +1512,11 @@ static bool run_test(void) {
|
||||||
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
||||||
CHECK(lardon3d_project_db_open(v8_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v8_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 18));
|
||||||
|
|
||||||
CHECK(create_v7_database(failed_v8_path));
|
CHECK(create_v7_database(failed_v8_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
|
||||||
|
|
@ -1461,7 +1533,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1480,11 +1552,11 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v10_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v10_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 18));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v10_path,
|
query_integer(v10_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
@ -1511,7 +1583,7 @@ static bool run_test(void) {
|
||||||
1));
|
1));
|
||||||
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1525,11 +1597,11 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v13_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v13_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 18));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v13_path,
|
query_integer(v13_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
@ -1552,7 +1624,7 @@ static bool run_test(void) {
|
||||||
true_v14_path);
|
true_v14_path);
|
||||||
}
|
}
|
||||||
CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK &&
|
CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(
|
CHECK(
|
||||||
|
|
@ -1667,7 +1739,7 @@ static bool run_test(void) {
|
||||||
if (true_v15_result != LARDON3D_PROJECT_DB_OK)
|
if (true_v15_result != LARDON3D_PROJECT_DB_OK)
|
||||||
fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error);
|
fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error);
|
||||||
CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK);
|
CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
Lardon3DProjectDbProject migrated_project;
|
Lardon3DProjectDbProject migrated_project;
|
||||||
CHECK(lardon3d_project_db_get_project(database, &migrated_project) ==
|
CHECK(lardon3d_project_db_get_project(database, &migrated_project) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
|
@ -1699,7 +1771,7 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(schema_compare(database_path, true_v15_path, true));
|
CHECK(schema_compare(database_path, true_v15_path, true));
|
||||||
|
|
@ -1734,7 +1806,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(true_v15_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(true_v15_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1753,7 +1825,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1772,7 +1844,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 17);
|
lardon3d_project_db_schema_version(database) == 18);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -2241,6 +2313,8 @@ static bool run_test(void) {
|
||||||
CHECK(unlink(true_v14_path) == 0);
|
CHECK(unlink(true_v14_path) == 0);
|
||||||
CHECK(unlink(true_v15_path) == 0);
|
CHECK(unlink(true_v15_path) == 0);
|
||||||
CHECK(unlink(failed_v15_path) == 0);
|
CHECK(unlink(failed_v15_path) == 0);
|
||||||
|
CHECK(unlink(v17_path) == 0);
|
||||||
|
CHECK(unlink(failed_v18_path) == 0);
|
||||||
CHECK(rmdir(directory) == 0);
|
CHECK(rmdir(directory) == 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
@ -17,6 +18,37 @@
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
static void test_put_u32(unsigned char *record, size_t *offset,
|
||||||
|
uint32_t value) {
|
||||||
|
for (unsigned byte = 0; byte < 4; ++byte)
|
||||||
|
record[(*offset)++] = (unsigned char)(value >> (byte * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_put_u64(unsigned char *record, size_t *offset,
|
||||||
|
uint64_t value) {
|
||||||
|
for (unsigned byte = 0; byte < 8; ++byte)
|
||||||
|
record[(*offset)++] = (unsigned char)(value >> (byte * 8));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_h_identity(Lardon3DIncrementalReconstructionMetadata *metadata) {
|
||||||
|
unsigned char record[76] = {0};
|
||||||
|
unsigned int size = 0;
|
||||||
|
size_t offset = 8;
|
||||||
|
memcpy(record, "L3DHIDV1", 8);
|
||||||
|
test_put_u32(record, &offset, 1);
|
||||||
|
test_put_u64(record, &offset, metadata->base_reconstruction_id);
|
||||||
|
test_put_u64(record, &offset, metadata->extension_track_set_id);
|
||||||
|
test_put_u64(record, &offset, metadata->calibration_scope_id);
|
||||||
|
test_put_u32(record, &offset, metadata->incremental_kind);
|
||||||
|
test_put_u32(record, &offset, metadata->incremental_version);
|
||||||
|
memcpy(record + offset, metadata->parameter_fingerprint, 32);
|
||||||
|
offset += 32;
|
||||||
|
return offset == sizeof(record) &&
|
||||||
|
EVP_Digest(record, sizeof(record), metadata->scientific_identity,
|
||||||
|
&size, EVP_sha256(), NULL) == 1 &&
|
||||||
|
size == sizeof(metadata->scientific_identity);
|
||||||
|
}
|
||||||
|
|
||||||
static int raw_exec(const char *path, const char *sql) {
|
static int raw_exec(const char *path, const char *sql) {
|
||||||
sqlite3 *connection = NULL;
|
sqlite3 *connection = NULL;
|
||||||
char *message = NULL;
|
char *message = NULL;
|
||||||
|
|
@ -91,7 +123,7 @@ static bool run_test(void) {
|
||||||
Lardon3DProjectDb *db = NULL;
|
Lardon3DProjectDb *db = NULL;
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(db) == 17);
|
CHECK(lardon3d_project_db_schema_version(db) == 18);
|
||||||
|
|
||||||
Lardon3DProjectDbScanSet scanset;
|
Lardon3DProjectDbScanSet scanset;
|
||||||
CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) ==
|
CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) ==
|
||||||
|
|
@ -305,6 +337,92 @@ static bool run_test(void) {
|
||||||
Lardon3DSparseReconstruction reconstruction;
|
Lardon3DSparseReconstruction reconstruction;
|
||||||
CHECK(lardon3d_sparse_reconstruction_publish(
|
CHECK(lardon3d_sparse_reconstruction_publish(
|
||||||
db, &publication, &reconstruction) == LARDON3D_PROJECT_DB_OK);
|
db, &publication, &reconstruction) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(!reconstruction.has_derivation_identity &&
|
||||||
|
reconstruction.parameter_fingerprint[0] == 9);
|
||||||
|
Lardon3DIncrementalReconstructionMetadata h_metadata = {
|
||||||
|
.base_reconstruction_id = reconstruction.reconstruction_id,
|
||||||
|
.extension_track_set_id = published_tracks.track_set_id,
|
||||||
|
.calibration_scope_id = scope.scope_id,
|
||||||
|
.incremental_kind = 1,
|
||||||
|
.incremental_version = 1,
|
||||||
|
};
|
||||||
|
h_metadata.parameter_fingerprint[0] = 44;
|
||||||
|
CHECK(test_h_identity(&h_metadata));
|
||||||
|
memcpy(publication.parameter_fingerprint, h_metadata.parameter_fingerprint, 32);
|
||||||
|
Lardon3DSparseReconstruction h_reconstruction;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_publish(
|
||||||
|
db, &publication, &h_metadata, &h_reconstruction) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DIncrementalReconstructionMetadata loaded_h_metadata;
|
||||||
|
Lardon3DSparseReconstruction loaded_h_reconstruction;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
db, h_metadata.scientific_identity, &loaded_h_metadata,
|
||||||
|
&loaded_h_reconstruction) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(loaded_h_metadata.reconstruction_id == h_reconstruction.reconstruction_id);
|
||||||
|
CHECK(loaded_h_metadata.base_reconstruction_id == reconstruction.reconstruction_id);
|
||||||
|
CHECK(loaded_h_reconstruction.reconstruction_id == h_reconstruction.reconstruction_id);
|
||||||
|
CHECK(loaded_h_reconstruction.has_derivation_identity &&
|
||||||
|
memcmp(loaded_h_reconstruction.derivation_identity,
|
||||||
|
h_metadata.scientific_identity, 32) == 0 &&
|
||||||
|
memcmp(loaded_h_reconstruction.parameter_fingerprint,
|
||||||
|
h_metadata.parameter_fingerprint, 32) == 0);
|
||||||
|
CHECK(memcmp(loaded_h_metadata.parameter_fingerprint,
|
||||||
|
h_metadata.parameter_fingerprint, 32) == 0);
|
||||||
|
CHECK(memcmp(loaded_h_metadata.scientific_identity,
|
||||||
|
h_metadata.scientific_identity, 32) == 0);
|
||||||
|
Lardon3DSparseReconstruction duplicate_h;
|
||||||
|
Lardon3DSparsePublication mismatched_h_publication = publication;
|
||||||
|
mismatched_h_publication.parameter_fingerprint[0] ^= 0x80;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_publish(
|
||||||
|
db, &mismatched_h_publication, &h_metadata, &duplicate_h) ==
|
||||||
|
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
CHECK(raw_scalar_equals(path, "SELECT COUNT(*) FROM sparse_reconstructions", 2));
|
||||||
|
CHECK(raw_scalar_equals(path, "SELECT COUNT(*) FROM incremental_reconstructions", 1));
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_publish(
|
||||||
|
db, &publication, &h_metadata, &duplicate_h) ==
|
||||||
|
LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||||
|
|
||||||
|
publication.parameter_fingerprint[0] = 10;
|
||||||
|
Lardon3DSparseReconstruction second_base;
|
||||||
|
CHECK(lardon3d_sparse_reconstruction_publish(db, &publication, &second_base) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(!second_base.has_derivation_identity &&
|
||||||
|
second_base.parameter_fingerprint[0] == 10);
|
||||||
|
Lardon3DIncrementalReconstructionMetadata second_h_metadata = h_metadata;
|
||||||
|
second_h_metadata.base_reconstruction_id = second_base.reconstruction_id;
|
||||||
|
CHECK(test_h_identity(&second_h_metadata));
|
||||||
|
memcpy(publication.parameter_fingerprint,
|
||||||
|
second_h_metadata.parameter_fingerprint, 32);
|
||||||
|
Lardon3DSparseReconstruction second_h;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_publish(
|
||||||
|
db, &publication, &second_h_metadata, &second_h) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(second_h.reconstruction_id != h_reconstruction.reconstruction_id &&
|
||||||
|
second_h.has_derivation_identity &&
|
||||||
|
memcmp(second_h.parameter_fingerprint,
|
||||||
|
h_reconstruction.parameter_fingerprint, 32) == 0 &&
|
||||||
|
memcmp(second_h.derivation_identity,
|
||||||
|
h_reconstruction.derivation_identity, 32) != 0);
|
||||||
|
unsigned char valid_identity[32];
|
||||||
|
memcpy(valid_identity, h_metadata.scientific_identity, 32);
|
||||||
|
h_metadata.scientific_identity[0] ^= 0x80;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_publish(
|
||||||
|
db, &publication, &h_metadata, &loaded_h_reconstruction) ==
|
||||||
|
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
memcpy(h_metadata.scientific_identity, valid_identity, 32);
|
||||||
|
h_metadata.parameter_fingerprint[0] ^= 0x40;
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_publish(
|
||||||
|
db, &publication, &h_metadata, &loaded_h_reconstruction) ==
|
||||||
|
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
h_metadata.parameter_fingerprint[0] ^= 0x40;
|
||||||
|
publication.parameter_fingerprint[0] = h_metadata.parameter_fingerprint[0];
|
||||||
|
h_metadata.base_reconstruction_id = UINT64_C(999999);
|
||||||
|
CHECK(test_h_identity(&h_metadata));
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_publish(
|
||||||
|
db, &publication, &h_metadata, &loaded_h_reconstruction) ==
|
||||||
|
LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||||
|
CHECK(raw_scalar_equals(path, "SELECT COUNT(*) FROM sparse_reconstructions", 4));
|
||||||
|
publication.parameter_fingerprint[0] = 9;
|
||||||
Lardon3DSparseLandmarkPage landmark_page = {0};
|
Lardon3DSparseLandmarkPage landmark_page = {0};
|
||||||
Lardon3DSparseLandmark landmark_items[1];
|
Lardon3DSparseLandmark landmark_items[1];
|
||||||
landmark_page.items = landmark_items;
|
landmark_page.items = landmark_items;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <lardon3d/project_db.h>
|
#include <lardon3d/project_db.h>
|
||||||
|
#include <lardon3d/incremental_reconstruction_task.h>
|
||||||
#include <lardon3d/sparse_sfm_task.h>
|
#include <lardon3d/sparse_sfm_task.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +66,7 @@ int main() {
|
||||||
Lardon3DProjectDb *database = nullptr;
|
Lardon3DProjectDb *database = nullptr;
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||||
CHECK(lardon3d_project_db_open(path.c_str(), &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(path.c_str(), &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
CHECK(lardon3d_project_db_schema_version(database) == 18);
|
||||||
|
|
||||||
sqlite3 *raw = nullptr;
|
sqlite3 *raw = nullptr;
|
||||||
CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK);
|
CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK);
|
||||||
|
|
@ -74,7 +75,11 @@ int main() {
|
||||||
"verifier_kind,verifier_version,verifier_fingerprint,input_scope_hash,gvr_count,track_count,"
|
"verifier_kind,verifier_version,verifier_fingerprint,input_scope_hash,gvr_count,track_count,"
|
||||||
"created_at) VALUES(41,'track_builder',1,zeroblob(32),1,1,zeroblob(32),zeroblob(32),1,0,0);"
|
"created_at) VALUES(41,'track_builder',1,zeroblob(32),1,1,zeroblob(32),zeroblob(32),1,0,0);"
|
||||||
"INSERT INTO sparse_calibration_scopes(scope_id,scientific_hash,member_count) "
|
"INSERT INTO sparse_calibration_scopes(scope_id,scientific_hash,member_count) "
|
||||||
"VALUES(43,randomblob(32),1);";
|
"VALUES(43,randomblob(32),1);"
|
||||||
|
"INSERT INTO sparse_reconstructions(reconstruction_id,track_set_id,"
|
||||||
|
"calibration_scope_id,sfm_kind,sfm_version,parameter_fingerprint,component_count,"
|
||||||
|
"registered_image_count,landmark_count,reprojection_rmse_px,reprojection_median_px,"
|
||||||
|
"created_at) VALUES(42,41,43,1,1,zeroblob(32),1,2,1,0,0,0);";
|
||||||
CHECK(sqlite3_exec(raw, fixture_sql, nullptr, nullptr, nullptr) == SQLITE_OK);
|
CHECK(sqlite3_exec(raw, fixture_sql, nullptr, nullptr, nullptr) == SQLITE_OK);
|
||||||
sqlite3_close(raw);
|
sqlite3_close(raw);
|
||||||
|
|
||||||
|
|
@ -135,6 +140,30 @@ int main() {
|
||||||
sqlite3_close(raw);
|
sqlite3_close(raw);
|
||||||
CHECK(lardon3d_project_db_load_sparse_sfm_task(database, 101, &corrupt) ==
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(database, 101, &corrupt) ==
|
||||||
LARDON3D_PROJECT_DB_NOT_FOUND);
|
LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||||
|
|
||||||
|
Lardon3DTaskDurableSnapshot h_snapshot{};
|
||||||
|
h_snapshot.id = 200;
|
||||||
|
std::snprintf(h_snapshot.name, sizeof(h_snapshot.name), "Phase H payload");
|
||||||
|
h_snapshot.saved_state = TASK_PENDING;
|
||||||
|
h_snapshot.recovery_state = TASK_PENDING;
|
||||||
|
h_snapshot.estimate = {269484032, 0, 0, 0, 1, 1, 1, 0, 1,
|
||||||
|
LARDON3D_RESOURCE_TASK_CPU};
|
||||||
|
Lardon3DProjectDbIncrementalReconstructionTask h_payload{
|
||||||
|
200, 42, 41, 43, 1, 1, {}};
|
||||||
|
h_payload.parameter_fingerprint[0] = 77;
|
||||||
|
CHECK(lardon3d_project_db_record_incremental_reconstruction_task(
|
||||||
|
database, &h_snapshot, LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND,
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND_VERSION, nullptr,
|
||||||
|
&h_payload, 0) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DProjectDbIncrementalReconstructionTask loaded_h{};
|
||||||
|
CHECK(lardon3d_project_db_load_incremental_reconstruction_task(
|
||||||
|
database, 200, &loaded_h) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(loaded_h.task_id == 200 && loaded_h.base_reconstruction_id == 42 &&
|
||||||
|
loaded_h.extension_track_set_id == 41 &&
|
||||||
|
loaded_h.calibration_scope_id == 43 && loaded_h.incremental_kind == 1 &&
|
||||||
|
loaded_h.incremental_version == 1 &&
|
||||||
|
std::memcmp(loaded_h.parameter_fingerprint,
|
||||||
|
h_payload.parameter_fingerprint, 32) == 0);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
CHECK(unlink(path.c_str()) == 0);
|
CHECK(unlink(path.c_str()) == 0);
|
||||||
CHECK(rmdir(directory) == 0);
|
CHECK(rmdir(directory) == 0);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <map>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
@ -10,6 +12,8 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#include <lardon3d/app_state.h>
|
#include <lardon3d/app_state.h>
|
||||||
#include <lardon3d/feature_store.h>
|
#include <lardon3d/feature_store.h>
|
||||||
|
#include <lardon3d/incremental_reconstruction.h>
|
||||||
|
#include <lardon3d/incremental_reconstruction_task.h>
|
||||||
#include <lardon3d/project_db.h>
|
#include <lardon3d/project_db.h>
|
||||||
#include <lardon3d/resource_governor.h>
|
#include <lardon3d/resource_governor.h>
|
||||||
#include <lardon3d/sparse_sfm_model.h>
|
#include <lardon3d/sparse_sfm_model.h>
|
||||||
|
|
@ -38,7 +42,8 @@ namespace {
|
||||||
} \
|
} \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
constexpr size_t kImageCount = 3;
|
constexpr size_t kBaseImageCount = 3;
|
||||||
|
constexpr size_t kImageCount = 4;
|
||||||
constexpr size_t kTrackCount = 12;
|
constexpr size_t kTrackCount = 12;
|
||||||
|
|
||||||
struct Fixture {
|
struct Fixture {
|
||||||
|
|
@ -48,6 +53,7 @@ struct Fixture {
|
||||||
Lardon3DProjectDbScanSet scanset{};
|
Lardon3DProjectDbScanSet scanset{};
|
||||||
Lardon3DProjectDbImage images[kImageCount]{};
|
Lardon3DProjectDbImage images[kImageCount]{};
|
||||||
Lardon3DProjectDbFeatureSet feature_sets[kImageCount]{};
|
Lardon3DProjectDbFeatureSet feature_sets[kImageCount]{};
|
||||||
|
Lardon3DProjectDbTrackSet base_track_set{};
|
||||||
Lardon3DProjectDbTrackSet track_set{};
|
Lardon3DProjectDbTrackSet track_set{};
|
||||||
Lardon3DSparseCalibrationScope scope{};
|
Lardon3DSparseCalibrationScope scope{};
|
||||||
Lardon3DSparseIncrementalParameters parameters{};
|
Lardon3DSparseIncrementalParameters parameters{};
|
||||||
|
|
@ -58,12 +64,34 @@ Lardon3DResourcePolicy policy() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wait_state(Lardon3DTaskQueue *queue, uint64_t id, Lardon3DTaskState wanted) {
|
bool wait_state(Lardon3DTaskQueue *queue, uint64_t id, Lardon3DTaskState wanted) {
|
||||||
for (size_t attempt = 0; attempt < 4000000; ++attempt) {
|
timespec start{};
|
||||||
|
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) return false;
|
||||||
|
const int64_t deadline = start.tv_sec * INT64_C(1000000000) + start.tv_nsec +
|
||||||
|
INT64_C(10000000000);
|
||||||
|
for (;;) {
|
||||||
Lardon3DTaskSnapshot snapshot{};
|
Lardon3DTaskSnapshot snapshot{};
|
||||||
if (lardon3d_task_queue_get(queue, id, &snapshot) && snapshot.state == wanted) return true;
|
if (lardon3d_task_queue_get(queue, id, &snapshot) && snapshot.state == wanted) return true;
|
||||||
|
timespec now{};
|
||||||
|
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0 ||
|
||||||
|
now.tv_sec * INT64_C(1000000000) + now.tv_nsec >= deadline)
|
||||||
|
return false;
|
||||||
sched_yield();
|
sched_yield();
|
||||||
}
|
}
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
bool wait_reservations_released(Lardon3DResourceGovernor *governor) {
|
||||||
|
timespec start{};
|
||||||
|
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) return false;
|
||||||
|
const int64_t deadline = start.tv_sec * INT64_C(1000000000) + start.tv_nsec +
|
||||||
|
INT64_C(10000000000);
|
||||||
|
while (lardon3d_resource_governor_reservation_count(governor) != 0) {
|
||||||
|
timespec now{};
|
||||||
|
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0 ||
|
||||||
|
now.tv_sec * INT64_C(1000000000) + now.tv_nsec >= deadline)
|
||||||
|
return false;
|
||||||
|
sched_yield();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DSparseGeometryPoint2 project(const Lardon3DSparseGeometryCalibration &calibration,
|
Lardon3DSparseGeometryPoint2 project(const Lardon3DSparseGeometryCalibration &calibration,
|
||||||
|
|
@ -144,6 +172,8 @@ bool create_fixture(Fixture *fixture) {
|
||||||
auto pixel = project(geometry, camera, points[track]);
|
auto pixel = project(geometry, camera, points[track]);
|
||||||
keypoints[track].x = static_cast<float>(pixel.x);
|
keypoints[track].x = static_cast<float>(pixel.x);
|
||||||
keypoints[track].y = static_cast<float>(pixel.y);
|
keypoints[track].y = static_cast<float>(pixel.y);
|
||||||
|
if (camera == kImageCount - 1 && track == 0)
|
||||||
|
keypoints[track].x += 0.25F;
|
||||||
keypoints[track].size = 1.0F;
|
keypoints[track].size = 1.0F;
|
||||||
std::memset(descriptors.data() + track * 32, static_cast<int>(track + camera), 32);
|
std::memset(descriptors.data() + track * 32, static_cast<int>(track + camera), 32);
|
||||||
}
|
}
|
||||||
|
|
@ -202,6 +232,23 @@ bool create_fixture(Fixture *fixture) {
|
||||||
CHECK(lardon3d_project_db_create_track_set(fixture->state.project_db, &configuration,
|
CHECK(lardon3d_project_db_create_track_set(fixture->state.project_db, &configuration,
|
||||||
tracks.data(), tracks.size(),
|
tracks.data(), tracks.size(),
|
||||||
&fixture->track_set) == LARDON3D_PROJECT_DB_OK);
|
&fixture->track_set) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
std::vector<Lardon3DProjectDbTrackObservation> base_observations(
|
||||||
|
kTrackCount * kBaseImageCount);
|
||||||
|
std::vector<Lardon3DProjectDbTrack> base_tracks(kTrackCount);
|
||||||
|
for (size_t track = 0; track < kTrackCount; ++track) {
|
||||||
|
base_tracks[track].observation_count = kBaseImageCount;
|
||||||
|
base_tracks[track].observations =
|
||||||
|
base_observations.data() + track * kBaseImageCount;
|
||||||
|
for (size_t camera = 0; camera < kBaseImageCount; ++camera)
|
||||||
|
base_tracks[track].observations[camera] = {
|
||||||
|
fixture->feature_sets[camera].feature_set_id,
|
||||||
|
static_cast<uint32_t>(track), static_cast<uint32_t>(camera)};
|
||||||
|
}
|
||||||
|
configuration.parameter_fingerprint[0] = 1;
|
||||||
|
CHECK(lardon3d_project_db_create_track_set(
|
||||||
|
fixture->state.project_db, &configuration, base_tracks.data(),
|
||||||
|
base_tracks.size(), &fixture->base_track_set) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_sparse_incremental_parameters_default(&fixture->parameters));
|
CHECK(lardon3d_sparse_incremental_parameters_default(&fixture->parameters));
|
||||||
fixture->parameters.minimum_seed_tracks = 6;
|
fixture->parameters.minimum_seed_tracks = 6;
|
||||||
fixture->parameters.minimum_seed_landmarks = 6;
|
fixture->parameters.minimum_seed_landmarks = 6;
|
||||||
|
|
@ -219,7 +266,88 @@ void destroy_runtime(Fixture *fixture) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DSparseSfmTaskConfiguration configuration(Fixture *fixture) {
|
Lardon3DSparseSfmTaskConfiguration configuration(Fixture *fixture) {
|
||||||
return {fixture->track_set.track_set_id, fixture->scope.scope_id, fixture->parameters};
|
return {fixture->base_track_set.track_set_id, fixture->scope.scope_id,
|
||||||
|
fixture->parameters};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recompute_metrics(Fixture *fixture,
|
||||||
|
const Lardon3DSparseReconstruction &reconstruction,
|
||||||
|
double *rmse, double *median) {
|
||||||
|
std::map<uint64_t, Lardon3DSparseRegisteredImage> cameras;
|
||||||
|
std::map<uint64_t, Lardon3DSparseLandmark> landmarks;
|
||||||
|
Lardon3DSparseRegisteredImage camera_items[64]{};
|
||||||
|
Lardon3DSparseRegisteredImagePage camera_page{0, 64, 0, 0, camera_items};
|
||||||
|
CHECK(lardon3d_sparse_registered_image_list(
|
||||||
|
fixture->state.project_db, reconstruction.reconstruction_id, 0,
|
||||||
|
64, &camera_page) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
for (size_t index = 0; index < camera_page.count; ++index)
|
||||||
|
cameras.emplace(camera_items[index].image_id, camera_items[index]);
|
||||||
|
Lardon3DSparseLandmark landmark_items[64]{};
|
||||||
|
Lardon3DSparseLandmarkPage landmark_page{0, 64, 0, 0, landmark_items};
|
||||||
|
CHECK(lardon3d_sparse_landmark_list(
|
||||||
|
fixture->state.project_db, reconstruction.reconstruction_id, 0,
|
||||||
|
64, &landmark_page) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
for (size_t index = 0; index < landmark_page.count; ++index)
|
||||||
|
landmarks.emplace(landmark_items[index].track_id, landmark_items[index]);
|
||||||
|
Lardon3DSparseLandmarkObservation observation_items[64]{};
|
||||||
|
Lardon3DSparseObservationPage observation_page{0, 0, 64, 0, 0, 0,
|
||||||
|
observation_items};
|
||||||
|
CHECK(lardon3d_sparse_observation_list(
|
||||||
|
fixture->state.project_db, reconstruction.reconstruction_id, 0, 0,
|
||||||
|
64, &observation_page) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
std::vector<double> errors;
|
||||||
|
double sum = 0.0;
|
||||||
|
const auto geometry = Lardon3DSparseGeometryCalibration{
|
||||||
|
4000, 3000, 2000.0, 2000.0, 2000.0, 1500.0, 0.0, 0.0, 0.0, 0.0};
|
||||||
|
for (size_t index = 0; index < observation_page.count; ++index) {
|
||||||
|
const auto &observation = observation_items[index];
|
||||||
|
auto landmark = landmarks.find(observation.track_id);
|
||||||
|
const Lardon3DProjectDbFeatureSet *feature_set = nullptr;
|
||||||
|
size_t camera_index = 0;
|
||||||
|
for (; camera_index < kImageCount; ++camera_index)
|
||||||
|
if (fixture->feature_sets[camera_index].feature_set_id ==
|
||||||
|
observation.feature_set_id) {
|
||||||
|
feature_set = &fixture->feature_sets[camera_index];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
CHECK(landmark != landmarks.end() && feature_set != nullptr);
|
||||||
|
auto camera = cameras.find(fixture->images[camera_index].image_id);
|
||||||
|
CHECK(camera != cameras.end());
|
||||||
|
Lardon3DFeatureReader *reader = nullptr;
|
||||||
|
Lardon3DFeatureFileMetadata metadata{};
|
||||||
|
CHECK(lardon3d_feature_reader_open(fixture->root.c_str(), feature_set,
|
||||||
|
&reader, &metadata) ==
|
||||||
|
LARDON3D_FEATURE_STORE_OK);
|
||||||
|
Lardon3DFeatureKeypoint keypoint{};
|
||||||
|
const auto read_status = lardon3d_feature_reader_keypoints(
|
||||||
|
reader, observation.feature_index, &keypoint, 1);
|
||||||
|
lardon3d_feature_reader_close(reader);
|
||||||
|
CHECK(read_status == LARDON3D_FEATURE_STORE_OK);
|
||||||
|
const double *r = camera->second.rotation_cw;
|
||||||
|
const double *t = camera->second.translation_cw;
|
||||||
|
const double x = r[0] * landmark->second.x + r[1] * landmark->second.y +
|
||||||
|
r[2] * landmark->second.z + t[0];
|
||||||
|
const double y = r[3] * landmark->second.x + r[4] * landmark->second.y +
|
||||||
|
r[5] * landmark->second.z + t[1];
|
||||||
|
const double z = r[6] * landmark->second.x + r[7] * landmark->second.y +
|
||||||
|
r[8] * landmark->second.z + t[2];
|
||||||
|
CHECK(std::isfinite(z) && z > 1e-9);
|
||||||
|
const double dx = geometry.fx * x / z + geometry.cx - keypoint.x;
|
||||||
|
const double dy = geometry.fy * y / z + geometry.cy - keypoint.y;
|
||||||
|
const double squared = dx * dx + dy * dy;
|
||||||
|
CHECK(std::isfinite(squared) && std::isfinite(sum + squared));
|
||||||
|
sum += squared;
|
||||||
|
errors.push_back(std::sqrt(squared));
|
||||||
|
}
|
||||||
|
CHECK(!errors.empty());
|
||||||
|
std::sort(errors.begin(), errors.end());
|
||||||
|
*rmse = std::sqrt(sum / static_cast<double>(errors.size()));
|
||||||
|
const size_t middle = errors.size() / 2;
|
||||||
|
*median = errors.size() % 2
|
||||||
|
? errors[middle]
|
||||||
|
: errors[middle - 1] +
|
||||||
|
(errors[middle] - errors[middle - 1]) / 2.0;
|
||||||
|
return std::isfinite(*rmse) && std::isfinite(*median);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool test_lookup() {
|
bool test_lookup() {
|
||||||
|
|
@ -296,7 +424,8 @@ bool test_task() {
|
||||||
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&fixture.parameters, fingerprint));
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&fixture.parameters, fingerprint));
|
||||||
Lardon3DSparseReconstruction reconstruction{};
|
Lardon3DSparseReconstruction reconstruction{};
|
||||||
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
||||||
fixture.state.project_db, fixture.track_set.track_set_id, fixture.scope.scope_id, 1, 1,
|
fixture.state.project_db, fixture.base_track_set.track_set_id,
|
||||||
|
fixture.scope.scope_id, 1, 1,
|
||||||
fingerprint, &reconstruction) == LARDON3D_PROJECT_DB_OK);
|
fingerprint, &reconstruction) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(reconstruction.component_count == 1 && reconstruction.registered_image_count == 3 &&
|
CHECK(reconstruction.component_count == 1 && reconstruction.registered_image_count == 3 &&
|
||||||
reconstruction.landmark_count >= 6 &&
|
reconstruction.landmark_count >= 6 &&
|
||||||
|
|
@ -310,12 +439,150 @@ bool test_task() {
|
||||||
&observation_page) == LARDON3D_PROJECT_DB_OK);
|
&observation_page) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(observation_page.count >= 18);
|
CHECK(observation_page.count >= 18);
|
||||||
|
|
||||||
|
Lardon3DIncrementalReconstructionTaskConfiguration h_configuration{
|
||||||
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
||||||
|
fixture.scope.scope_id};
|
||||||
|
uint64_t h_task_id = 0;
|
||||||
|
Lardon3DTask *h_task = lardon3d_project_create_incremental_reconstruction_task(
|
||||||
|
&fixture.state, &h_configuration, &h_task_id);
|
||||||
|
CHECK(h_task && h_task_id != 0);
|
||||||
|
CHECK(lardon3d_task_kind(h_task, kind, &kind_version));
|
||||||
|
CHECK(std::strcmp(kind, LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND) == 0 &&
|
||||||
|
kind_version == 1);
|
||||||
|
Lardon3DResourceEstimate h_estimate{};
|
||||||
|
CHECK(lardon3d_task_resource_estimate(h_task, &h_estimate));
|
||||||
|
CHECK(h_estimate.minimum_batch_size == 1 && h_estimate.maximum_batch_size == 1 &&
|
||||||
|
h_estimate.desired_cpu_threads == 1 && h_estimate.desired_gpu_slots == 0 &&
|
||||||
|
h_estimate.desired_io_slots == 1 &&
|
||||||
|
h_estimate.task_class == LARDON3D_RESOURCE_TASK_CPU);
|
||||||
|
Lardon3DProjectDbIncrementalReconstructionTask h_payload{};
|
||||||
|
CHECK(lardon3d_project_db_load_incremental_reconstruction_task(
|
||||||
|
fixture.state.project_db, h_task_id, &h_payload) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
unsigned char h_fingerprint[32]{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_parameter_fingerprint(h_fingerprint));
|
||||||
|
CHECK(std::memcmp(h_payload.parameter_fingerprint, h_fingerprint, 32) == 0);
|
||||||
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, h_task, nullptr));
|
||||||
|
CHECK(wait_state(fixture.state.task_queue, h_task_id, TASK_COMPLETED));
|
||||||
|
CHECK(wait_reservations_released(fixture.state.resource_governor));
|
||||||
|
Lardon3DIncrementalReconstructionIdentity h_identity{
|
||||||
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
||||||
|
fixture.scope.scope_id, 1, 1, {}};
|
||||||
|
std::memcpy(h_identity.parameter_fingerprint, h_fingerprint, 32);
|
||||||
|
unsigned char h_digest[32]{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(&h_identity,
|
||||||
|
h_digest));
|
||||||
|
Lardon3DIncrementalReconstructionMetadata h_metadata{};
|
||||||
|
Lardon3DSparseReconstruction h_reconstruction{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
fixture.state.project_db, h_digest, &h_metadata,
|
||||||
|
&h_reconstruction) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(h_metadata.base_reconstruction_id == reconstruction.reconstruction_id);
|
||||||
|
CHECK(h_reconstruction.reconstruction_id != reconstruction.reconstruction_id &&
|
||||||
|
h_reconstruction.registered_image_count == kImageCount &&
|
||||||
|
h_reconstruction.landmark_count == reconstruction.landmark_count &&
|
||||||
|
h_reconstruction.reprojection_rmse_px > 0.0 &&
|
||||||
|
h_reconstruction.reprojection_median_px >= 0.0);
|
||||||
|
double expected_h_rmse = 0.0;
|
||||||
|
double expected_h_median = 0.0;
|
||||||
|
CHECK(recompute_metrics(&fixture, h_reconstruction, &expected_h_rmse,
|
||||||
|
&expected_h_median));
|
||||||
|
CHECK(std::abs(h_reconstruction.reprojection_rmse_px - expected_h_rmse) <
|
||||||
|
1e-12 &&
|
||||||
|
std::abs(h_reconstruction.reprojection_median_px - expected_h_median) <
|
||||||
|
1e-12);
|
||||||
|
CHECK(std::abs(h_reconstruction.reprojection_rmse_px -
|
||||||
|
reconstruction.reprojection_rmse_px) > 1e-12);
|
||||||
|
|
||||||
|
Lardon3DSparseCalibration incompatible_calibration{};
|
||||||
|
incompatible_calibration.model_kind =
|
||||||
|
LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE;
|
||||||
|
incompatible_calibration.model_version =
|
||||||
|
LARDON3D_SPARSE_SFM_CALIBRATION_VERSION;
|
||||||
|
incompatible_calibration.width = 4000;
|
||||||
|
incompatible_calibration.height = 3000;
|
||||||
|
incompatible_calibration.fx = 2100.0;
|
||||||
|
incompatible_calibration.fy = 2100.0;
|
||||||
|
incompatible_calibration.cx = 2000.0;
|
||||||
|
incompatible_calibration.cy = 1500.0;
|
||||||
|
incompatible_calibration.provenance_kind =
|
||||||
|
LARDON3D_SPARSE_SFM_PROVENANCE_USER_EXPLICIT;
|
||||||
|
Lardon3DSparseCalibration incompatible_stored{};
|
||||||
|
CHECK(lardon3d_sparse_calibration_create(
|
||||||
|
fixture.state.project_db, &incompatible_calibration,
|
||||||
|
&incompatible_stored) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DSparseCalibrationMember incompatible_members[kImageCount]{};
|
||||||
|
for (size_t index = 0; index < kImageCount; ++index) {
|
||||||
|
incompatible_members[index].image_id = fixture.images[index].image_id;
|
||||||
|
incompatible_members[index].calibration_id = incompatible_stored.calibration_id;
|
||||||
|
std::memcpy(incompatible_members[index].calibration_hash,
|
||||||
|
incompatible_stored.scientific_hash, 32);
|
||||||
|
}
|
||||||
|
Lardon3DSparseCalibrationScope incompatible_scope{};
|
||||||
|
CHECK(lardon3d_sparse_calibration_scope_create(
|
||||||
|
fixture.state.project_db, incompatible_members, kImageCount,
|
||||||
|
&incompatible_scope) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DIncrementalReconstructionTaskConfiguration incompatible_h{
|
||||||
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
||||||
|
incompatible_scope.scope_id};
|
||||||
|
uint64_t incompatible_task_id = 0;
|
||||||
|
CHECK(lardon3d_project_enqueue_incremental_reconstruction_task(
|
||||||
|
&fixture.state, &incompatible_h, &incompatible_task_id));
|
||||||
|
CHECK(wait_state(fixture.state.task_queue, incompatible_task_id, TASK_FAILED));
|
||||||
|
CHECK(wait_reservations_released(fixture.state.resource_governor));
|
||||||
|
Lardon3DIncrementalReconstructionIdentity incompatible_identity{
|
||||||
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
||||||
|
incompatible_scope.scope_id, 1, 1, {}};
|
||||||
|
std::memcpy(incompatible_identity.parameter_fingerprint, h_fingerprint, 32);
|
||||||
|
unsigned char incompatible_digest[32]{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(
|
||||||
|
&incompatible_identity, incompatible_digest));
|
||||||
|
Lardon3DIncrementalReconstructionMetadata missing_metadata{};
|
||||||
|
Lardon3DSparseReconstruction missing_reconstruction{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
fixture.state.project_db, incompatible_digest, &missing_metadata,
|
||||||
|
&missing_reconstruction) == LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||||
|
|
||||||
|
uint64_t h_restart_id = 0;
|
||||||
|
Lardon3DTask *h_pending =
|
||||||
|
lardon3d_project_create_incremental_reconstruction_task(
|
||||||
|
&fixture.state, &h_configuration, &h_restart_id);
|
||||||
|
CHECK(h_pending != nullptr);
|
||||||
|
Lardon3DTaskDurableSnapshot h_before{};
|
||||||
|
CHECK(lardon3d_task_durable_snapshot(h_pending, &h_before));
|
||||||
|
lardon3d_task_destroy(h_pending);
|
||||||
|
Lardon3DTaskKindDescriptor h_descriptor{
|
||||||
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND, 1,
|
||||||
|
lardon3d_incremental_reconstruction_task_reconstruct};
|
||||||
|
Lardon3DTaskKindRegistry h_registry{};
|
||||||
|
CHECK(lardon3d_task_kind_registry_init(&h_registry, &h_descriptor, 1));
|
||||||
|
Lardon3DTaskReconstructionContext h_runtime{
|
||||||
|
fixture.root.c_str(), fixture.state.project_db,
|
||||||
|
fixture.state.resource_governor, nullptr};
|
||||||
|
Lardon3DTask *h_restored = nullptr;
|
||||||
|
CHECK(lardon3d_task_kind_registry_restore(
|
||||||
|
&h_registry, LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND, 1,
|
||||||
|
&h_before, &h_runtime, &h_restored) == LARDON3D_TASK_KIND_OK);
|
||||||
|
Lardon3DResourceEstimate h_restored_estimate{};
|
||||||
|
CHECK(lardon3d_task_resource_estimate(h_restored, &h_restored_estimate));
|
||||||
|
CHECK(std::memcmp(&h_estimate, &h_restored_estimate,
|
||||||
|
sizeof(h_estimate)) == 0);
|
||||||
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, h_restored, nullptr));
|
||||||
|
CHECK(wait_state(fixture.state.task_queue, h_restart_id, TASK_COMPLETED));
|
||||||
|
CHECK(wait_reservations_released(fixture.state.resource_governor));
|
||||||
|
Lardon3DIncrementalReconstructionMetadata reused_h_metadata{};
|
||||||
|
Lardon3DSparseReconstruction reused_h{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
fixture.state.project_db, h_digest, &reused_h_metadata, &reused_h) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(reused_h.reconstruction_id == h_reconstruction.reconstruction_id);
|
||||||
|
|
||||||
uint64_t reuse_id = 0;
|
uint64_t reuse_id = 0;
|
||||||
CHECK(lardon3d_project_enqueue_sparse_sfm_task(&fixture.state, &config, &reuse_id));
|
CHECK(lardon3d_project_enqueue_sparse_sfm_task(&fixture.state, &config, &reuse_id));
|
||||||
CHECK(wait_state(fixture.state.task_queue, reuse_id, TASK_COMPLETED));
|
CHECK(wait_state(fixture.state.task_queue, reuse_id, TASK_COMPLETED));
|
||||||
Lardon3DSparseReconstruction reused{};
|
Lardon3DSparseReconstruction reused{};
|
||||||
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
||||||
fixture.state.project_db, fixture.track_set.track_set_id, fixture.scope.scope_id, 1, 1,
|
fixture.state.project_db, fixture.base_track_set.track_set_id,
|
||||||
|
fixture.scope.scope_id, 1, 1,
|
||||||
fingerprint, &reused) == LARDON3D_PROJECT_DB_OK);
|
fingerprint, &reused) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(reused.reconstruction_id == reconstruction.reconstruction_id);
|
CHECK(reused.reconstruction_id == reconstruction.reconstruction_id);
|
||||||
|
|
||||||
|
|
@ -331,6 +598,17 @@ bool test_task() {
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||||
CHECK(lardon3d_project_db_open(fixture.database_path.c_str(), &fixture.state.project_db, error) ==
|
CHECK(lardon3d_project_db_open(fixture.database_path.c_str(), &fixture.state.project_db, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DIncrementalReconstructionMetadata reopened_h_metadata{};
|
||||||
|
Lardon3DSparseReconstruction reopened_h{};
|
||||||
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
||||||
|
fixture.state.project_db, h_digest, &reopened_h_metadata,
|
||||||
|
&reopened_h) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(reopened_h.reconstruction_id == h_reconstruction.reconstruction_id &&
|
||||||
|
reopened_h_metadata.base_reconstruction_id ==
|
||||||
|
reconstruction.reconstruction_id &&
|
||||||
|
reopened_h.registered_image_count == kImageCount &&
|
||||||
|
reopened_h.reprojection_rmse_px ==
|
||||||
|
h_reconstruction.reprojection_rmse_px);
|
||||||
Lardon3DTaskKindDescriptor descriptor{LARDON3D_SPARSE_SFM_TASK_KIND, 1,
|
Lardon3DTaskKindDescriptor descriptor{LARDON3D_SPARSE_SFM_TASK_KIND, 1,
|
||||||
lardon3d_sparse_sfm_task_reconstruct};
|
lardon3d_sparse_sfm_task_reconstruct};
|
||||||
Lardon3DTaskKindRegistry registry{};
|
Lardon3DTaskKindRegistry registry{};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue