feat(reconstruction): add geometric verification model
This commit is contained in:
parent
d97291ad7b
commit
558f00cd6f
15 changed files with 1227 additions and 38 deletions
|
|
@ -27,7 +27,8 @@ Lardon3D ne vise pas simplement "dossier de photos → objet 3D", mais "ensemble
|
||||||
- **Image View** : vues triées et filtrées pour la TUI
|
- **Image View** : vues triées et filtrées pour la TUI
|
||||||
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences
|
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences
|
||||||
- **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre
|
- **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre
|
||||||
- **Project Database v11** : tâches, catalogue, Feature Store, matching et tâches Matcher durables
|
- **Project Database v12** : tâches, catalogue, matching et résultats géométriques durables
|
||||||
|
- **Geometric Verification Model v1** : identité, masque d'inliers et modèle 3×3 persistants
|
||||||
- **Task Kind Registry** : identité métier durable et reconstruction runtime explicite
|
- **Task Kind Registry** : identité métier durable et reconstruction runtime explicite
|
||||||
- **Recovery projet** : reprise automatique sélective et bornée des imports récupérables
|
- **Recovery projet** : reprise automatique sélective et bornée des imports récupérables
|
||||||
- **Task Queue** : file FIFO avec sélection adaptative et backpressure
|
- **Task Queue** : file FIFO avec sélection adaptative et backpressure
|
||||||
|
|
@ -111,6 +112,7 @@ Acquisitions
|
||||||
- [Candidate Pair](docs/architecture/candidate_pair.md)
|
- [Candidate Pair](docs/architecture/candidate_pair.md)
|
||||||
- [Match Result](docs/architecture/match_result.md)
|
- [Match Result](docs/architecture/match_result.md)
|
||||||
- [Matcher](docs/architecture/matcher.md)
|
- [Matcher](docs/architecture/matcher.md)
|
||||||
|
- [Geometric Verification](docs/architecture/geometric_verification.md)
|
||||||
- [Backend Vulkan ORB](docs/architecture/vulkan_matcher.md)
|
- [Backend Vulkan ORB](docs/architecture/vulkan_matcher.md)
|
||||||
- [Viewer](docs/architecture/viewer.md)
|
- [Viewer](docs/architecture/viewer.md)
|
||||||
- [Revue des fondations](docs/architecture/foundation_review.md)
|
- [Revue des fondations](docs/architecture/foundation_review.md)
|
||||||
|
|
@ -152,11 +154,12 @@ Pour les changements sensibles à la mémoire ou à la concurrence, ajouter ASan
|
||||||
|
|
||||||
Lardon3D est en développement actif. La persistance des tâches, le catalogue,
|
Lardon3D est en développement actif. La persistance des tâches, le catalogue,
|
||||||
le Feature Store multipasse, le Visual Index ORB, Candidate Pair Generator
|
le Feature Store multipasse, le Visual Index ORB, Candidate Pair Generator
|
||||||
et Matcher v1 sont implémentés. Le runtime Feature + Matcher emploie des tâches
|
et Matcher v1 sont implémentés. Geometric Verification Model v1 est implémenté ;
|
||||||
|
le Geometric Verifier reste planifié. Le runtime Feature + Matcher emploie des tâches
|
||||||
durables, de petits lots, le Resource Governor interactif et un hot path Vulkan
|
durables, de petits lots, le Resource Governor interactif et un hot path Vulkan
|
||||||
ORB exact avec fallback CPU. La feasibility Vulkan SIFT/RootSIFT a été rejetée ;
|
ORB exact avec fallback CPU. La feasibility Vulkan SIFT/RootSIFT a été rejetée ;
|
||||||
ces deux matchers restent sur OpenCV L2. DAG générique,
|
ces deux matchers restent sur OpenCV L2. DAG générique,
|
||||||
vérification géométrique, SfM et viewer restent des tickets séparés planifiés.
|
calcul de vérification géométrique, SfM et viewer restent des tickets séparés planifiés.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
|
|
|
||||||
175
docs/architecture/geometric_verification.md
Normal file
175
docs/architecture/geometric_verification.md
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
# Geometric Verification
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Geometric Verification Model v1 est le contrat scientifique persistant placé après le Matcher.
|
||||||
|
Il stocke un résultat terminé, compact et immutable. Il n'est ni un moteur de calcul ni une tâche.
|
||||||
|
Aucun RANSAC, USAC, MAGSAC, calcul d'inliers ou backend géométrique n'appartient à ce ticket.
|
||||||
|
|
||||||
|
## Position in reconstruction pipeline
|
||||||
|
|
||||||
|
La chaîne d'ownership est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Feature Set → Candidate Pair → Match Result → Geometric Verification Result
|
||||||
|
```
|
||||||
|
|
||||||
|
Le masque indexe exclusivement l'ordre des entrées du Match File canonique du Match Result. Il
|
||||||
|
n'indexe directement ni les features, ni la Candidate Pair, ni un ordre temporaire de backend.
|
||||||
|
|
||||||
|
## Scientific ownership
|
||||||
|
|
||||||
|
Le parent scientifique est `match_result_id`. L'API accepte uniquement un Match Result existant,
|
||||||
|
`MATCHED`, avec `match_count` strictement positif. `NO_MATCH` et les erreurs runtime ne peuvent pas
|
||||||
|
produire de résultat géométrique.
|
||||||
|
|
||||||
|
## Parent Match Result
|
||||||
|
|
||||||
|
Le Match Store reste propriétaire de la validation du Match File. La création consulte le parent
|
||||||
|
et son `match_count` en DB ; elle ne relit pas l'asset. Un load valide aussi l'existence et l'état du
|
||||||
|
parent afin qu'une ligne corrompue ne soit jamais rendue comme résultat valide.
|
||||||
|
|
||||||
|
## Persistent identity
|
||||||
|
|
||||||
|
L'identité demandée et unique est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(match_result_id, verifier_kind, verifier_version, parameter_fingerprint)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le fingerprint est le SHA-256 opaque de 32 octets déjà standard dans le projet. Il représentera
|
||||||
|
un encodage de paramètres versionné, stable, à ordre de champs explicite et, pour les nombres
|
||||||
|
binaires, little-endian. Aucun timestamp, résultat, PID, durée ou identifiant matériel n'y entre.
|
||||||
|
|
||||||
|
## Verifier kind
|
||||||
|
|
||||||
|
v1 supporte uniquement `FUNDAMENTAL`, valeur persistante stable 1. Aucun comportement fictif
|
||||||
|
`ESSENTIAL` ou `HOMOGRAPHY` n'est réservé dans l'API publique.
|
||||||
|
|
||||||
|
## Persistent states
|
||||||
|
|
||||||
|
- `GEOMETRIC_REJECTED=1` : calcul scientifique terminé, critère non satisfait ;
|
||||||
|
- `GEOMETRIC_VERIFIED=2` : calcul scientifique terminé, critère satisfait.
|
||||||
|
|
||||||
|
`FAILED`, `RUNNING`, `PAUSED` et `CANCELLED` appartiennent au Task Runtime. REJECTED peut conserver
|
||||||
|
un nombre d'inliers non nul.
|
||||||
|
|
||||||
|
## Model representation
|
||||||
|
|
||||||
|
FUNDAMENTAL utilise neuf colonnes SQLite `REAL`, en ordre ligne-major `m00` à `m22`. SQLite
|
||||||
|
convertit les valeurs numériques en binary64 sans exposer une ABI C. VERIFIED exige les neuf
|
||||||
|
valeurs présentes et finies. REJECTED exige les neuf valeurs NULL. Le modèle n'impose ni rang 2,
|
||||||
|
ni déterminant, ni normalisation ou échelle canonique ; ces règles relèvent du futur verifier.
|
||||||
|
|
||||||
|
## Inlier representation
|
||||||
|
|
||||||
|
Le masque est un BLOB SQLite obligatoire de taille exacte `ceil(match_count / 8)`. Pour l'entrée
|
||||||
|
`i`, `byte_index=i/8`, `bit_index=i%8` et le masque vaut `1u << bit_index`. Le bit 0 est donc le bit
|
||||||
|
de poids faible de l'octet 0. Cette convention est indépendante de l'endianness CPU et de l'ABI.
|
||||||
|
Les bits de padding du dernier octet valent zéro et le popcount est exactement `inlier_count`.
|
||||||
|
|
||||||
|
Le masque existe pour REJECTED comme pour VERIFIED. Avec 8192 matches, il mesure au maximum
|
||||||
|
1024 octets. Un BLOB SQLite évite les milliers de lignes secondaires et la publication, le hash,
|
||||||
|
le nettoyage et la récupération d'un asset externe d'environ 1 Kio. Une liste `uint32_t` serait
|
||||||
|
jusqu'à 32 fois plus grande au cas dense et aurait un encodage supplémentaire à versionner.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- `0 <= inlier_count <= parent.match_count <= 8192` ;
|
||||||
|
- longueur, padding et popcount du masque sont canoniques ;
|
||||||
|
- REJECTED possède un masque cohérent et aucun modèle ;
|
||||||
|
- VERIFIED possède un masque cohérent et exactement neuf valeurs finies ;
|
||||||
|
- kind, version et fingerprint ont une sérialisation stable ;
|
||||||
|
- une ligne publiée est complète et immutable.
|
||||||
|
|
||||||
|
Exemple : pour 100 matches, FUNDAMENTAL v1/fingerprint X peut publier REJECTED avec 23 inliers,
|
||||||
|
un masque de 13 octets et aucun modèle. Une autre identité peut publier VERIFIED avec 67 inliers,
|
||||||
|
le même format de masque et une matrice 3×3 finie.
|
||||||
|
|
||||||
|
## Persistence semantics
|
||||||
|
|
||||||
|
Une création valide puis insère identité, état, masque et modèle dans une transaction courte. Le
|
||||||
|
calcul futur se fera entièrement avant cette transaction. SQLite fournit l'atomicité ; aucun asset
|
||||||
|
ou journal secondaire n'est créé.
|
||||||
|
|
||||||
|
## Reuse
|
||||||
|
|
||||||
|
Le reuse cherche uniquement l'identité exacte, jamais le résultat le plus récent. Une identité
|
||||||
|
existante retourne une erreur de contrainte à `create`; le runtime fera `find`, validera puis
|
||||||
|
réutilisera. `INSERT OR REPLACE` est interdit, même si le nouveau contenu semble identique.
|
||||||
|
|
||||||
|
## Invalidations
|
||||||
|
|
||||||
|
Un nouveau Match Result possède un nouvel ID et ne réutilise donc aucun ancien résultat
|
||||||
|
géométrique. La FK emploie `ON DELETE CASCADE` : supprimer explicitement le parent supprime ses
|
||||||
|
enfants et ne crée pas d'orphelin. Aucun moteur d'invalidation parallèle n'est nécessaire.
|
||||||
|
|
||||||
|
## Project DB schema
|
||||||
|
|
||||||
|
Project DB v12 ajoute `geometric_verification_results`, une contrainte UNIQUE sur l'identité et un
|
||||||
|
index de pagination `(match_result_id, geometric_verification_result_id)`. Les CHECK SQL portent
|
||||||
|
les bornes scalaires, tailles locales et nullabilité modèle/état. La cohérence avec le parent, le
|
||||||
|
padding, le popcount et la finitude restent validés en C.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
L'API publique implémente :
|
||||||
|
|
||||||
|
- `lardon3d_project_db_create_geometric_verification_result()` ;
|
||||||
|
- `lardon3d_project_db_load_geometric_verification_result()` ;
|
||||||
|
- `lardon3d_project_db_find_geometric_verification_result()` ;
|
||||||
|
- `lardon3d_project_db_list_geometric_verification_results()`.
|
||||||
|
|
||||||
|
La liste est bornée à 256 entrées, filtrée par parent puis ordonnée par ID croissant avec curseur.
|
||||||
|
Le résultat en mémoire contient son `created_at` et son masque dans une capacité fixe de 1024
|
||||||
|
octets : aucun ownership dynamique ni fonction de destruction. Les fonctions copient fingerprint,
|
||||||
|
masque et neuf coefficients ; l'appelant conserve ses entrées.
|
||||||
|
|
||||||
|
Parent absent retourne `NOT_FOUND`; parent NO_MATCH ou parent incohérent retourne `CONSTRAINT` à
|
||||||
|
la création. Masque, modèle ou arguments locaux invalides retournent `INVALID_ARGUMENT`; duplicate
|
||||||
|
identity retourne `CONSTRAINT`. Un loader qui rencontre une ligne ou un parent incohérent retourne
|
||||||
|
`CORRUPT`, sans résultat partiel.
|
||||||
|
|
||||||
|
## Resource bounds
|
||||||
|
|
||||||
|
Un résultat contient au plus 1024 octets de masque et 72 octets de valeurs numériques, plus de
|
||||||
|
petites métadonnées. Une page est bornée. Le loader vérifie les entiers et tailles SQLite avant
|
||||||
|
tout cast ou copie. Il n'existe ni cache global, ni lecture non bornée, ni Content Store associé.
|
||||||
|
Le Match File parent mesure au plus 98 336 octets ; le futur job peut donc rester une petite unité.
|
||||||
|
|
||||||
|
## Error ownership
|
||||||
|
|
||||||
|
Seuls les résultats scientifiques terminés sont persistés. OOM, exception, annulation, timeout,
|
||||||
|
device lost, I/O transitoire ou panne de thread appartiennent à l'exécution de tâche. État du modèle
|
||||||
|
et état d'exécution sont deux contrats distincts.
|
||||||
|
|
||||||
|
## Recovery semantics
|
||||||
|
|
||||||
|
Après commit, le résultat est complet et réutilisable après réouverture. Avant commit, le rollback
|
||||||
|
ne laisse aucune ligne partielle. Un loader rejette toute ligne incohérente comme corruption au
|
||||||
|
lieu de réparer ou d'interpréter au mieux.
|
||||||
|
|
||||||
|
## Future verifier execution contract
|
||||||
|
|
||||||
|
Le prochain ticket prendra un Match Result et son Match File borné. L'accès nécessaire existe via
|
||||||
|
`lardon3d_feature_reader_keypoints()`, borné à 256 keypoints par appel ; l'intégration devra relier
|
||||||
|
les deux Feature Sets et les indices du Match File sans modifier le Feature Store. Le verifier
|
||||||
|
estimera hors transaction, dérivera état/masque/modèle, publiera en une courte transaction,
|
||||||
|
checkpoint puis libérera les buffers. Une paire est l'unité atomique. Task Runtime et Resource
|
||||||
|
Governor décideront admission, threads et lots ; zram/swap ne sont jamais un budget.
|
||||||
|
|
||||||
|
Un backend reste hors identité seulement s'il est scientifiquement transparent. Sinon son
|
||||||
|
algorithme ou contrat doit apparaître dans kind/version/fingerprint avant publication. Toute seed
|
||||||
|
influençant le résultat doit avoir une politique déterministe versionnée ou être couverte par le
|
||||||
|
fingerprint. Aucun nombre de threads ou hardware ID n'est un paramètre scientifique par défaut.
|
||||||
|
|
||||||
|
## Explicitly out of scope
|
||||||
|
|
||||||
|
Le calcul géométrique, le choix RANSAC/USAC/MAGSAC, OpenCV geometry, GPU, Vulkan, OpenCL, shader,
|
||||||
|
task kind, worker, checkpoint et nouvelle orchestration sont explicitement hors périmètre.
|
||||||
|
|
||||||
|
## Versioning
|
||||||
|
|
||||||
|
Project DB schema version 12 décrit le stockage. `verifier_version` décrit indépendamment le
|
||||||
|
contrat scientifique. Changer un algorithme n'impose une migration DB que si la représentation
|
||||||
|
persistante change.
|
||||||
|
|
@ -61,4 +61,7 @@ contrainte `UNIQUE`.
|
||||||
Le prochain modèle possède ses propres statistiques géométriques, dont un
|
Le prochain modèle possède ses propres statistiques géométriques, dont un
|
||||||
éventuel `inlier_count`.
|
éventuel `inlier_count`.
|
||||||
|
|
||||||
NEXT: GEOMETRIC VERIFICATION MODEL
|
Geometric Verification Model v1 est désormais l'enfant scientifique persistant
|
||||||
|
de ce résultat dans Project DB v12.
|
||||||
|
|
||||||
|
NEXT: GEOMETRIC VERIFIER v1
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,10 @@ estimation couvrant ce working set ; aucune seconde logique de budget n'est
|
||||||
introduite. Sa ligne durable `matcher_tasks` appartient au schéma Project DB
|
introduite. Sa ligne durable `matcher_tasks` appartient au schéma Project DB
|
||||||
v11 ; le Match Result reste le contrat publié en v10.
|
v11 ; le Match Result reste le contrat publié en v10.
|
||||||
|
|
||||||
|
Project DB v12 ajoute un enfant `Geometric Verification Result` référencé par
|
||||||
|
`match_result_id`. Le Matcher ne calcule, ne stocke et ne valide aucun inlier
|
||||||
|
géométrique ; seul son Match File canonique définit l'ordre indexé par le masque.
|
||||||
|
|
||||||
### Backend Vulkan ORB
|
### Backend Vulkan ORB
|
||||||
|
|
||||||
La frontière évaluée remplace uniquement KNN Hamming par un compute top-2 : un
|
La frontière évaluée remplace uniquement KNN Hamming par un compute top-2 : un
|
||||||
|
|
@ -113,4 +117,4 @@ mêmes paires attendues, l'ordre et la sérialisation stables. Aucune promesse
|
||||||
bit-à-bit cross-platform n'est faite pour les distances L2 flottantes.
|
bit-à-bit cross-platform n'est faite pour les distances L2 flottantes.
|
||||||
ORB/Hamming bénéficie d'une garantie plus forte grâce à sa distance entière.
|
ORB/Hamming bénéficie d'une garantie plus forte grâce à sa distance entière.
|
||||||
|
|
||||||
NEXT: GEOMETRIC VERIFICATION MODEL
|
NEXT: GEOMETRIC VERIFIER v1
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
# Base de données projet Lardon3D
|
# Base de données projet Lardon3D
|
||||||
|
|
||||||
> Version courante : **v11**. La migration transactionnelle v10→v11 ajoute
|
> Version courante : **v12**. La migration transactionnelle v11→v12 ajoute le
|
||||||
|
> modèle immutable `geometric_verification_results`. La migration v10→v11 ajoute
|
||||||
> `matcher_tasks` pour la tâche Matcher durable. La version v10 publiée ajoute
|
> `matcher_tasks` pour la tâche Matcher durable. La version v10 publiée ajoute
|
||||||
> uniquement `match_results` pour le Match Result Model. La migration v8→v9
|
> uniquement `match_results` pour le Match Result Model. La migration v8→v9
|
||||||
> ajoute la table `candidate_pair_generate_tasks` pour la tâche durable
|
> ajoute la table `candidate_pair_generate_tasks` pour la tâche durable
|
||||||
|
|
@ -396,19 +397,65 @@ CREATE INDEX match_results_feature_set_b_idx
|
||||||
- `lardon3d_project_db_record_matcher_task()` — UPSERT configuration/curseur
|
- `lardon3d_project_db_record_matcher_task()` — UPSERT configuration/curseur
|
||||||
- `lardon3d_project_db_load_matcher_task()` — SELECT par task_id
|
- `lardon3d_project_db_load_matcher_task()` — SELECT par task_id
|
||||||
|
|
||||||
|
## Schéma v12 implémenté
|
||||||
|
|
||||||
|
La migration v11→v12 ajoute uniquement `geometric_verification_results`. Le
|
||||||
|
parent est un Match Result `MATCHED`; sa validation interligne reste dans l'API.
|
||||||
|
|
||||||
|
Schéma abrégé (la chaîne SQL exécutable canonique reste dans `src/project_db.c`) :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE geometric_verification_results(
|
||||||
|
geometric_verification_result_id INTEGER PRIMARY KEY AUTOINCREMENT
|
||||||
|
CHECK(geometric_verification_result_id>0),
|
||||||
|
match_result_id INTEGER NOT NULL
|
||||||
|
REFERENCES match_results(match_result_id) ON DELETE CASCADE,
|
||||||
|
verifier_kind INTEGER NOT NULL CHECK(verifier_kind=1),
|
||||||
|
verifier_version INTEGER NOT NULL
|
||||||
|
CHECK(verifier_version>0 AND verifier_version<=4294967295),
|
||||||
|
parameter_fingerprint BLOB NOT NULL
|
||||||
|
CHECK(length(parameter_fingerprint)=32),
|
||||||
|
status INTEGER NOT NULL CHECK(status IN (1,2)),
|
||||||
|
inlier_count INTEGER NOT NULL
|
||||||
|
CHECK(inlier_count>=0 AND inlier_count<=8192),
|
||||||
|
inlier_mask BLOB NOT NULL
|
||||||
|
CHECK(length(inlier_mask)>=1 AND length(inlier_mask)<=1024),
|
||||||
|
model_m00 REAL, model_m01 REAL, model_m02 REAL,
|
||||||
|
model_m10 REAL, model_m11 REAL, model_m12 REAL,
|
||||||
|
model_m20 REAL, model_m21 REAL, model_m22 REAL,
|
||||||
|
created_at INTEGER NOT NULL CHECK(created_at>=0),
|
||||||
|
CHECK(/* REJECTED: neuf NULL ; VERIFIED: neuf non-NULL */),
|
||||||
|
UNIQUE(match_result_id, verifier_kind, verifier_version,
|
||||||
|
parameter_fingerprint)
|
||||||
|
);
|
||||||
|
CREATE INDEX geometric_verification_results_parent_idx
|
||||||
|
ON geometric_verification_results(
|
||||||
|
match_result_id, geometric_verification_result_id
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
Les valeurs stables sont FUNDAMENTAL=1, GEOMETRIC_REJECTED=1 et
|
||||||
|
GEOMETRIC_VERIFIED=2. REJECTED interdit le modèle ; VERIFIED exige neuf valeurs
|
||||||
|
finies. Pour les deux états, l'API impose taille canonique, padding nul et
|
||||||
|
popcount exact du masque, ainsi que `inlier_count <= parent.match_count`.
|
||||||
|
L'index parent sert la liste paginée ; la contrainte UNIQUE sert le find exact.
|
||||||
|
Le contrat complet, dont l'ordre des bits, est dans
|
||||||
|
`geometric_verification.md`.
|
||||||
|
|
||||||
## Ouverture et migrations
|
## Ouverture et migrations
|
||||||
|
|
||||||
Une DB vide reçoit la chaîne de schémas jusqu'à v11 dans une transaction
|
Une DB vide reçoit la chaîne de schémas jusqu'à v12 dans une transaction
|
||||||
`BEGIN IMMEDIATE`. Une DB v1 reçoit transactionnellement les colonnes nullable
|
`BEGIN IMMEDIATE`. Une DB v1 reçoit transactionnellement les colonnes nullable
|
||||||
`task_kind` et `task_kind_version`, puis les migrations v2→v3. Les anciennes lignes restent
|
`task_kind` et `task_kind_version`, puis les migrations v2→v3. Les anciennes lignes restent
|
||||||
`NULL/NULL`, sans type inventé et sans perte des projets, tâches, checkpoints ou
|
`NULL/NULL`, sans type inventé et sans perte des projets, tâches, checkpoints ou
|
||||||
artefacts. Une interruption ou erreur provoque un rollback complet. Les DB v1,
|
artefacts. Une interruption ou erreur provoque un rollback complet. Les DB v1,
|
||||||
v2, v3, v4, v5, v6, v7, v8, v9 et v10 sont migrées séquentiellement vers v11.
|
v2, v3, v4, v5, v6, v7, v8, v9, v10 et v11 sont migrées séquentiellement vers v12.
|
||||||
Une v10 publiée est validée comme telle avant que v10→v11 crée
|
Une v10 publiée est validée comme telle avant que v10→v11 crée
|
||||||
`matcher_tasks` ; son absence n'est donc pas une corruption. Une version future est refusée et une DB contenant
|
`matcher_tasks` ; son absence n'est donc pas une corruption. Une version future est refusée et une DB contenant
|
||||||
des tables sans métadonnée de version est considérée corrompue. La fonction
|
des tables sans métadonnée de version est considérée corrompue. La fonction
|
||||||
interne de migration applique uniquement la chaîne séquentielle connue jusqu'à
|
interne de migration applique uniquement la chaîne séquentielle connue jusqu'à
|
||||||
v11 ; une valeur hors de 1..11 est refusée.
|
v12 ; une valeur hors de 1..12 est refusée. La failure injectée v12 rollbacke
|
||||||
|
la table, l'index et le changement de version, laissant une vraie v11 utilisable.
|
||||||
|
|
||||||
Migration v1→v2 exacte, exécutée entre `BEGIN IMMEDIATE` et `COMMIT` :
|
Migration v1→v2 exacte, exécutée entre `BEGIN IMMEDIATE` et `COMMIT` :
|
||||||
|
|
||||||
|
|
@ -545,8 +592,8 @@ ouvert.
|
||||||
|
|
||||||
## Statut
|
## Statut
|
||||||
|
|
||||||
**IMPLEMENTED** — SQLite système, schéma v11 et migrations séquentielles
|
**IMPLEMENTED** — SQLite système, schéma v12 et migrations séquentielles
|
||||||
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11, identité projet, transactions
|
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11→v12, 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,
|
||||||
|
|
|
||||||
|
|
@ -97,10 +97,20 @@ Index, persistance, canonicalisation, idempotence et tâche durable.
|
||||||
| **Orchestration** | `matcher.run` traite les Candidate Pairs par pages et lots durables de 1/2/4/8. |
|
| **Orchestration** | `matcher.run` traite les Candidate Pairs par pages et lots durables de 1/2/4/8. |
|
||||||
|
|
||||||
**Statut :** IMPLEMENTED v1 — Matcher, Match Store, reprise idempotente et Task
|
**Statut :** IMPLEMENTED v1 — Matcher, Match Store, reprise idempotente et Task
|
||||||
durable. La vérification géométrique reste l'étape suivante, non commencée.
|
durable. Le calcul Geometric Verifier reste l'étape suivante, non commencée.
|
||||||
Le Match Result appartient à Project DB v10 et la tâche durable `matcher.run`
|
Le Match Result appartient à Project DB v10 et la tâche durable `matcher.run`
|
||||||
à Project DB v11.
|
à Project DB v11.
|
||||||
|
|
||||||
|
### F2. Geometric Verification
|
||||||
|
|
||||||
|
Project DB v12 implémente uniquement le modèle scientifique persistant. Chaque
|
||||||
|
résultat appartient à un Match Result `MATCHED`, possède une identité exacte,
|
||||||
|
un masque compact borné et, si VERIFIED, une matrice fondamentale 3×3 finie.
|
||||||
|
La publication est atomique et immutable. Le calcul, son task kind et le choix
|
||||||
|
d'algorithme restent planifiés dans Geometric Verifier v1.
|
||||||
|
|
||||||
|
**Statut :** MODEL IMPLEMENTED v1 — VERIFIER PLANNED.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### G. Tracks
|
### G. Tracks
|
||||||
|
|
@ -229,7 +239,8 @@ Image Catalog (B) ──► Feature Store (C)
|
||||||
## Statut du pipeline
|
## Statut du pipeline
|
||||||
|
|
||||||
Import, Image Catalog, Feature Extraction, Feature Store, Visual Index,
|
Import, Image Catalog, Feature Extraction, Feature Store, Visual Index,
|
||||||
Candidate Pair et Matching v1 sont **IMPLEMENTED**. Geometric Verification,
|
Candidate Pair, Matching v1 et Geometric Verification Model v1 sont
|
||||||
|
**IMPLEMENTED**. Geometric Verifier,
|
||||||
Tracks et SfM sont **PLANNED**.
|
Tracks et SfM sont **PLANNED**.
|
||||||
|
|
||||||
Ce document décrit la vision architecturale cible du pipeline de
|
Ce document décrit la vision architecturale cible du pipeline de
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,14 @@ chaque paire et entre les lots. Un crash après publication mais avant le
|
||||||
checkpoint revoit la paire : le Matcher réutilise alors le Match Result et ne
|
checkpoint revoit la paire : le Matcher réutilise alors le Match Result et ne
|
||||||
recalcule pas les descripteurs.
|
recalcule pas les descripteurs.
|
||||||
|
|
||||||
|
## Geometric Verification Model
|
||||||
|
|
||||||
|
Project DB v12 stocke un résultat borné à 1024 octets de masque et neuf
|
||||||
|
binary64. Le modèle ne lit aucun signal matériel et ne modifie pas le Governor.
|
||||||
|
Le futur verifier calculera une paire hors transaction, publiera par une courte
|
||||||
|
transaction, checkpoint puis libérera ses buffers avant réadmission. Admission,
|
||||||
|
threads et lots resteront exclusivement décidés par Runtime et Governor.
|
||||||
|
|
||||||
## GPU et files
|
## GPU et files
|
||||||
|
|
||||||
La Radeon 780M est UMA : toute mémoire GPU compte aussi comme pression RAM. Un
|
La Radeon 780M est UMA : toute mémoire GPU compte aussi comme pression RAM. Un
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,11 @@ par lots adaptatifs de 1, 2, 4 ou 8. Il persiste le curseur
|
||||||
effectue une rupture de séquence avant le suivant. Une paire repassée après un
|
effectue une rupture de séquence avant le suivant. Une paire repassée après un
|
||||||
crash est réutilisée par son Match Result.
|
crash est réutilisée par son Match Result.
|
||||||
|
|
||||||
|
Geometric Verification Model v1 n'ajoute aucun task kind ni état runtime.
|
||||||
|
Project DB v12 ne contient que les deux états scientifiques terminaux. Le futur
|
||||||
|
Geometric Verifier v1 devra obtenir réservation, pause/cancel, lot et checkpoint
|
||||||
|
dans un ticket séparé avant toute exécution.
|
||||||
|
|
||||||
Le chemin de production de l'import ne possède plus de thread ni de drapeau
|
Le chemin de production de l'import ne possède plus de thread ni de drapeau
|
||||||
d'annulation privés. Son wrapper TUI ne fait qu'enqueue/cancel/observer la
|
d'annulation privés. Son wrapper TUI ne fait qu'enqueue/cancel/observer la
|
||||||
tâche générique. Chaque callback traite un lot borné, checkpoint hors mutex de
|
tâche générique. Chaque callback traite un lot borné, checkpoint hors mutex de
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
#include <lardon3d/task.h>
|
#include <lardon3d/task.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 11,
|
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 12,
|
||||||
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,
|
||||||
|
|
@ -18,6 +18,9 @@ enum {
|
||||||
LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX = 256,
|
LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX = 256,
|
||||||
LARDON3D_PROJECT_DB_CANDIDATE_PAIR_PAGE_MAX = 256,
|
LARDON3D_PROJECT_DB_CANDIDATE_PAIR_PAGE_MAX = 256,
|
||||||
LARDON3D_PROJECT_DB_MATCH_RESULT_PAGE_MAX = 256,
|
LARDON3D_PROJECT_DB_MATCH_RESULT_PAGE_MAX = 256,
|
||||||
|
LARDON3D_PROJECT_DB_GEOMETRIC_RESULT_PAGE_MAX = 256,
|
||||||
|
LARDON3D_PROJECT_DB_INLIER_MASK_MAX = 1024,
|
||||||
|
LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS = 9,
|
||||||
LARDON3D_PROJECT_DB_SCANSET_NAME_CAPACITY = 256,
|
LARDON3D_PROJECT_DB_SCANSET_NAME_CAPACITY = 256,
|
||||||
LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY = 256,
|
LARDON3D_PROJECT_DB_IMAGE_NAME_CAPACITY = 256,
|
||||||
LARDON3D_PROJECT_DB_SHA256_SIZE = 32,
|
LARDON3D_PROJECT_DB_SHA256_SIZE = 32,
|
||||||
|
|
@ -158,6 +161,30 @@ typedef struct {
|
||||||
int64_t created_at;
|
int64_t created_at;
|
||||||
} Lardon3DProjectDbMatchResult;
|
} Lardon3DProjectDbMatchResult;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL = 1,
|
||||||
|
} Lardon3DGeometricVerifierKind;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_GEOMETRIC_REJECTED = 1,
|
||||||
|
LARDON3D_GEOMETRIC_VERIFIED = 2,
|
||||||
|
} Lardon3DGeometricVerificationStatus;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t geometric_verification_result_id;
|
||||||
|
uint64_t match_result_id;
|
||||||
|
Lardon3DGeometricVerifierKind verifier_kind;
|
||||||
|
uint32_t verifier_version;
|
||||||
|
unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
|
Lardon3DGeometricVerificationStatus status;
|
||||||
|
uint32_t inlier_count;
|
||||||
|
size_t inlier_mask_size;
|
||||||
|
unsigned char inlier_mask[LARDON3D_PROJECT_DB_INLIER_MASK_MAX];
|
||||||
|
bool has_model;
|
||||||
|
double model[LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS];
|
||||||
|
int64_t created_at;
|
||||||
|
} Lardon3DProjectDbGeometricVerificationResult;
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LARDON3D_PROJECT_DB_IMAGE_REGISTERED = 0,
|
LARDON3D_PROJECT_DB_IMAGE_REGISTERED = 0,
|
||||||
LARDON3D_PROJECT_DB_IMAGE_ALREADY_PRESENT
|
LARDON3D_PROJECT_DB_IMAGE_ALREADY_PRESENT
|
||||||
|
|
@ -507,4 +534,24 @@ Lardon3DProjectDbResult lardon3d_project_db_list_match_results(
|
||||||
Lardon3DProjectDb *database, uint64_t after_match_result_id,
|
Lardon3DProjectDb *database, uint64_t after_match_result_id,
|
||||||
Lardon3DProjectDbMatchResult *results, size_t capacity, size_t *count);
|
Lardon3DProjectDbMatchResult *results, size_t capacity, size_t *count);
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
||||||
|
Lardon3DGeometricVerifierKind verifier_kind, uint32_t verifier_version,
|
||||||
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
||||||
|
Lardon3DGeometricVerificationStatus status, uint32_t inlier_count,
|
||||||
|
const unsigned char *inlier_mask, size_t inlier_mask_size, const double *model,
|
||||||
|
int64_t created_at, Lardon3DProjectDbGeometricVerificationResult *result);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verification_result(
|
||||||
|
Lardon3DProjectDb *database, uint64_t geometric_verification_result_id,
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *result);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_find_geometric_verification_result(
|
||||||
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
||||||
|
Lardon3DGeometricVerifierKind verifier_kind, uint32_t verifier_version,
|
||||||
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *result);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_list_geometric_verification_results(
|
||||||
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
||||||
|
uint64_t after_geometric_verification_result_id,
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *results, size_t capacity, size_t *count);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
16
meson.build
16
meson.build
|
|
@ -507,6 +507,22 @@ match_result_test = executable(
|
||||||
|
|
||||||
test('match-result', match_result_test, timeout: 30)
|
test('match-result', match_result_test, timeout: 30)
|
||||||
|
|
||||||
|
geometric_verification_test = executable(
|
||||||
|
'test-geometric-verification',
|
||||||
|
sources: [
|
||||||
|
'tests/test_geometric_verification.c',
|
||||||
|
'src/project_db.c',
|
||||||
|
'src/task.c',
|
||||||
|
'src/resource_governor.c',
|
||||||
|
'src/resource_snapshot.c',
|
||||||
|
],
|
||||||
|
c_args: ['-DLARDON3D_PROJECT_DB_TESTING'],
|
||||||
|
include_directories: include_directories('include'),
|
||||||
|
dependencies: [threads, sqlite3, openssl],
|
||||||
|
)
|
||||||
|
|
||||||
|
test('geometric-verification', geometric_verification_test, timeout: 30)
|
||||||
|
|
||||||
project_test = executable(
|
project_test = executable(
|
||||||
'test-project',
|
'test-project',
|
||||||
sources: [
|
sources: [
|
||||||
|
|
|
||||||
406
src/project_db.c
406
src/project_db.c
|
|
@ -238,6 +238,31 @@ static const char schema_matcher_task_v11[] =
|
||||||
"matcher_kind INTEGER NOT NULL CHECK(matcher_kind BETWEEN 0 AND 2),"
|
"matcher_kind INTEGER NOT NULL CHECK(matcher_kind BETWEEN 0 AND 2),"
|
||||||
"ratio_threshold REAL NOT NULL CHECK(ratio_threshold>0.0 AND ratio_threshold<1.0));";
|
"ratio_threshold REAL NOT NULL CHECK(ratio_threshold>0.0 AND ratio_threshold<1.0));";
|
||||||
|
|
||||||
|
static const char schema_geometric_verification_v12[] =
|
||||||
|
"CREATE TABLE geometric_verification_results("
|
||||||
|
"geometric_verification_result_id INTEGER PRIMARY KEY AUTOINCREMENT "
|
||||||
|
"CHECK(geometric_verification_result_id>0),"
|
||||||
|
"match_result_id INTEGER NOT NULL REFERENCES match_results(match_result_id) ON DELETE CASCADE,"
|
||||||
|
"verifier_kind INTEGER NOT NULL CHECK(verifier_kind=1),"
|
||||||
|
"verifier_version INTEGER NOT NULL CHECK(verifier_version>0 AND verifier_version<=4294967295),"
|
||||||
|
"parameter_fingerprint BLOB NOT NULL CHECK(length(parameter_fingerprint)=32),"
|
||||||
|
"status INTEGER NOT NULL CHECK(status IN (1,2)),"
|
||||||
|
"inlier_count INTEGER NOT NULL CHECK(inlier_count>=0 AND inlier_count<=8192),"
|
||||||
|
"inlier_mask BLOB NOT NULL CHECK(length(inlier_mask)>=1 AND length(inlier_mask)<=1024),"
|
||||||
|
"model_m00 REAL,model_m01 REAL,model_m02 REAL,"
|
||||||
|
"model_m10 REAL,model_m11 REAL,model_m12 REAL,"
|
||||||
|
"model_m20 REAL,model_m21 REAL,model_m22 REAL,"
|
||||||
|
"created_at INTEGER NOT NULL CHECK(created_at>=0),"
|
||||||
|
"CHECK((status=1 AND model_m00 IS NULL AND model_m01 IS NULL AND model_m02 IS NULL AND "
|
||||||
|
"model_m10 IS NULL AND model_m11 IS NULL AND model_m12 IS NULL AND model_m20 IS NULL AND "
|
||||||
|
"model_m21 IS NULL AND model_m22 IS NULL) OR "
|
||||||
|
"(status=2 AND model_m00 IS NOT NULL AND model_m01 IS NOT NULL AND model_m02 IS NOT NULL AND "
|
||||||
|
"model_m10 IS NOT NULL AND model_m11 IS NOT NULL AND model_m12 IS NOT NULL AND "
|
||||||
|
"model_m20 IS NOT NULL AND model_m21 IS NOT NULL AND model_m22 IS NOT NULL)),"
|
||||||
|
"UNIQUE(match_result_id,verifier_kind,verifier_version,parameter_fingerprint));"
|
||||||
|
"CREATE INDEX geometric_verification_results_parent_idx ON "
|
||||||
|
"geometric_verification_results(match_result_id,geometric_verification_result_id);";
|
||||||
|
|
||||||
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 : "");
|
||||||
|
|
@ -327,7 +352,7 @@ 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 != 8 && from_version != 9 && from_version != 10 && from_version != 11) {
|
||||||
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");
|
||||||
|
|
@ -568,6 +593,22 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
||||||
"finish schema v11 migration");
|
"finish schema v11 migration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 12) {
|
||||||
|
result = execute(database, schema_geometric_verification_v12,
|
||||||
|
"migrate schema v11 to v12");
|
||||||
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||||
|
const char *forced_failure = getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V12");
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && forced_failure && strcmp(forced_failure, "1") == 0) {
|
||||||
|
result = execute(database, "INSERT INTO missing_test_table VALUES(1)",
|
||||||
|
"forced migration v12 failure");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = execute(database,
|
||||||
|
"UPDATE metadata SET value=12 WHERE key='schema_version' AND value=11",
|
||||||
|
"finish schema v12 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");
|
||||||
}
|
}
|
||||||
|
|
@ -682,7 +723,8 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
|
||||||
"feature_support_members",
|
"feature_support_members",
|
||||||
"candidate_pairs",
|
"candidate_pairs",
|
||||||
"matcher_tasks",
|
"matcher_tasks",
|
||||||
"match_results"};
|
"match_results",
|
||||||
|
"geometric_verification_results"};
|
||||||
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) {
|
||||||
|
|
@ -3751,6 +3793,366 @@ Lardon3DProjectDbResult lardon3d_project_db_list_match_results(
|
||||||
return db_result;
|
return db_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t geometric_mask_size(uint32_t match_count) {
|
||||||
|
return ((size_t)match_count + 7U) / 8U;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t geometric_mask_popcount(const unsigned char *mask, size_t size) {
|
||||||
|
uint32_t count = 0;
|
||||||
|
for (size_t byte_index = 0; byte_index < size; ++byte_index) {
|
||||||
|
unsigned int value = mask[byte_index];
|
||||||
|
while (value != 0U) {
|
||||||
|
value &= value - 1U;
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_geometric_mask(const unsigned char *mask, size_t size, uint32_t match_count,
|
||||||
|
uint32_t inlier_count) {
|
||||||
|
size_t expected_size = geometric_mask_size(match_count);
|
||||||
|
if (!mask || size != expected_size || expected_size == 0 ||
|
||||||
|
expected_size > LARDON3D_PROJECT_DB_INLIER_MASK_MAX || inlier_count > match_count) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unsigned int used_bits = match_count % 8U;
|
||||||
|
if (used_bits != 0U) {
|
||||||
|
unsigned int padding_mask = 0xffU << used_bits;
|
||||||
|
if (((unsigned int)mask[size - 1] & padding_mask) != 0U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return geometric_mask_popcount(mask, size) == inlier_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_geometric_model(Lardon3DGeometricVerificationStatus status,
|
||||||
|
const double *model) {
|
||||||
|
if (status == LARDON3D_GEOMETRIC_REJECTED) {
|
||||||
|
return model == NULL;
|
||||||
|
}
|
||||||
|
if (status != LARDON3D_GEOMETRIC_VERIFIED || !model) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS; ++index) {
|
||||||
|
if (!isfinite(model[index])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char geometric_result_columns[] =
|
||||||
|
"g.geometric_verification_result_id,g.match_result_id,g.verifier_kind,"
|
||||||
|
"g.verifier_version,g.parameter_fingerprint,g.status,g.inlier_count,g.inlier_mask,"
|
||||||
|
"g.model_m00,g.model_m01,g.model_m02,g.model_m10,g.model_m11,g.model_m12,"
|
||||||
|
"g.model_m20,g.model_m21,g.model_m22,g.created_at,m.result_status,m.match_count ";
|
||||||
|
|
||||||
|
static bool read_geometric_result(sqlite3_stmt *statement,
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
||||||
|
if (!statement || !result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int column = 0; column <= 7; ++column) {
|
||||||
|
int expected_type = column == 4 || column == 7 ? SQLITE_BLOB : SQLITE_INTEGER;
|
||||||
|
if (sqlite3_column_type(statement, column) != expected_type) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sqlite3_column_type(statement, 17) != SQLITE_INTEGER ||
|
||||||
|
sqlite3_column_type(statement, 18) != SQLITE_INTEGER ||
|
||||||
|
sqlite3_column_type(statement, 19) != SQLITE_INTEGER) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
||||||
|
sqlite3_int64 parent_id = sqlite3_column_int64(statement, 1);
|
||||||
|
sqlite3_int64 kind = sqlite3_column_int64(statement, 2);
|
||||||
|
sqlite3_int64 version = sqlite3_column_int64(statement, 3);
|
||||||
|
sqlite3_int64 status = sqlite3_column_int64(statement, 5);
|
||||||
|
sqlite3_int64 inlier_count = sqlite3_column_int64(statement, 6);
|
||||||
|
sqlite3_int64 created_at = sqlite3_column_int64(statement, 17);
|
||||||
|
sqlite3_int64 parent_status = sqlite3_column_int64(statement, 18);
|
||||||
|
sqlite3_int64 match_count = sqlite3_column_int64(statement, 19);
|
||||||
|
int fingerprint_size = sqlite3_column_bytes(statement, 4);
|
||||||
|
int mask_size = sqlite3_column_bytes(statement, 7);
|
||||||
|
const void *fingerprint = sqlite3_column_blob(statement, 4);
|
||||||
|
const void *mask = sqlite3_column_blob(statement, 7);
|
||||||
|
if (id <= 0 || parent_id <= 0 || kind != LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL ||
|
||||||
|
version <= 0 || version > UINT32_MAX ||
|
||||||
|
(status != LARDON3D_GEOMETRIC_REJECTED && status != LARDON3D_GEOMETRIC_VERIFIED) ||
|
||||||
|
inlier_count < 0 || inlier_count > 8192 || created_at < 0 ||
|
||||||
|
parent_status != LARDON3D_MATCH_RESULT_STATUS_MATCHED || match_count <= 0 ||
|
||||||
|
match_count > 8192 || fingerprint_size != LARDON3D_PROJECT_DB_SHA256_SIZE ||
|
||||||
|
!fingerprint || mask_size <= 0 || mask_size > LARDON3D_PROJECT_DB_INLIER_MASK_MAX || !mask ||
|
||||||
|
!valid_geometric_mask(mask, (size_t)mask_size, (uint32_t)match_count,
|
||||||
|
(uint32_t)inlier_count)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
double model[LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS] = {0};
|
||||||
|
bool has_model = status == LARDON3D_GEOMETRIC_VERIFIED;
|
||||||
|
for (int index = 0; index < LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS; ++index) {
|
||||||
|
int type = sqlite3_column_type(statement, 8 + index);
|
||||||
|
if ((!has_model && type != SQLITE_NULL) ||
|
||||||
|
(has_model && type != SQLITE_FLOAT && type != SQLITE_INTEGER)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (has_model) {
|
||||||
|
model[index] = sqlite3_column_double(statement, 8 + index);
|
||||||
|
if (!isfinite(model[index])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
memset(result, 0, sizeof(*result));
|
||||||
|
result->geometric_verification_result_id = (uint64_t)id;
|
||||||
|
result->match_result_id = (uint64_t)parent_id;
|
||||||
|
result->verifier_kind = (Lardon3DGeometricVerifierKind)kind;
|
||||||
|
result->verifier_version = (uint32_t)version;
|
||||||
|
memcpy(result->parameter_fingerprint, fingerprint, LARDON3D_PROJECT_DB_SHA256_SIZE);
|
||||||
|
result->status = (Lardon3DGeometricVerificationStatus)status;
|
||||||
|
result->inlier_count = (uint32_t)inlier_count;
|
||||||
|
result->inlier_mask_size = (size_t)mask_size;
|
||||||
|
memcpy(result->inlier_mask, mask, (size_t)mask_size);
|
||||||
|
result->has_model = has_model;
|
||||||
|
memcpy(result->model, model, sizeof(model));
|
||||||
|
result->created_at = created_at;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Lardon3DProjectDbResult read_single_geometric_result(
|
||||||
|
Lardon3DProjectDb *database, sqlite3_stmt *statement,
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE) {
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
return LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
}
|
||||||
|
if (code != SQLITE_ROW) {
|
||||||
|
Lardon3DProjectDbResult db_result = sqlite_result(database, code, "read geometric result");
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
return db_result;
|
||||||
|
}
|
||||||
|
bool valid = read_geometric_result(statement, result);
|
||||||
|
code = sqlite3_step(statement);
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
return valid && code == SQLITE_DONE ? LARDON3D_PROJECT_DB_OK : LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
||||||
|
Lardon3DGeometricVerifierKind verifier_kind, uint32_t verifier_version,
|
||||||
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
||||||
|
Lardon3DGeometricVerificationStatus status, uint32_t inlier_count,
|
||||||
|
const unsigned char *inlier_mask, size_t inlier_mask_size, const double *model,
|
||||||
|
int64_t created_at, Lardon3DProjectDbGeometricVerificationResult *result) {
|
||||||
|
if (!database || !valid_catalog_id(match_result_id) ||
|
||||||
|
verifier_kind != LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL || verifier_version == 0 ||
|
||||||
|
!parameter_fingerprint || inlier_count > 8192 || !inlier_mask || inlier_mask_size == 0 ||
|
||||||
|
inlier_mask_size > LARDON3D_PROJECT_DB_INLIER_MASK_MAX ||
|
||||||
|
!valid_geometric_model(status, model) || created_at < 0 || !result) {
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
memset(result, 0, sizeof(*result));
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
Lardon3DProjectDbResult db_result =
|
||||||
|
execute(database, "BEGIN IMMEDIATE", "begin geometric result creation");
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
uint32_t match_count = 0;
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
db_result = prepare(database,
|
||||||
|
"SELECT result_status,match_count FROM match_results "
|
||||||
|
"WHERE match_result_id=?1",
|
||||||
|
&statement);
|
||||||
|
}
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
if (code == SQLITE_DONE) {
|
||||||
|
db_result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
} else if (code != SQLITE_ROW || sqlite3_column_type(statement, 0) != SQLITE_INTEGER ||
|
||||||
|
sqlite3_column_type(statement, 1) != SQLITE_INTEGER) {
|
||||||
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
} else {
|
||||||
|
sqlite3_int64 parent_status = sqlite3_column_int64(statement, 0);
|
||||||
|
sqlite3_int64 parent_count = sqlite3_column_int64(statement, 1);
|
||||||
|
if (parent_status != LARDON3D_MATCH_RESULT_STATUS_MATCHED || parent_count <= 0 ||
|
||||||
|
parent_count > 8192) {
|
||||||
|
db_result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||||
|
} else {
|
||||||
|
match_count = (uint32_t)parent_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
!valid_geometric_mask(inlier_mask, inlier_mask_size, match_count, inlier_count)) {
|
||||||
|
db_result = LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
db_result = prepare(
|
||||||
|
database,
|
||||||
|
"INSERT INTO geometric_verification_results("
|
||||||
|
"match_result_id,verifier_kind,verifier_version,parameter_fingerprint,status,"
|
||||||
|
"inlier_count,inlier_mask,model_m00,model_m01,model_m02,model_m10,model_m11,model_m12,"
|
||||||
|
"model_m20,model_m21,model_m22,created_at) "
|
||||||
|
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17)",
|
||||||
|
&statement);
|
||||||
|
}
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
||||||
|
(void)sqlite3_bind_int(statement, 2, (int)verifier_kind);
|
||||||
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)verifier_version);
|
||||||
|
(void)sqlite3_bind_blob(statement, 4, parameter_fingerprint,
|
||||||
|
LARDON3D_PROJECT_DB_SHA256_SIZE, SQLITE_TRANSIENT);
|
||||||
|
(void)sqlite3_bind_int(statement, 5, (int)status);
|
||||||
|
(void)sqlite3_bind_int64(statement, 6, (sqlite3_int64)inlier_count);
|
||||||
|
(void)sqlite3_bind_blob(statement, 7, inlier_mask, (int)inlier_mask_size, SQLITE_TRANSIENT);
|
||||||
|
for (int index = 0; index < LARDON3D_PROJECT_DB_FUNDAMENTAL_COEFFICIENTS; ++index) {
|
||||||
|
if (model) {
|
||||||
|
(void)sqlite3_bind_double(statement, 8 + index, model[index]);
|
||||||
|
} else {
|
||||||
|
(void)sqlite3_bind_null(statement, 8 + index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(void)sqlite3_bind_int64(statement, 17, created_at);
|
||||||
|
db_result = step_done(database, statement, "create geometric result");
|
||||||
|
statement = NULL;
|
||||||
|
}
|
||||||
|
uint64_t result_id = 0;
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_int64 inserted_id = sqlite3_last_insert_rowid(database->connection);
|
||||||
|
if (inserted_id <= 0) {
|
||||||
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
} else {
|
||||||
|
result_id = (uint64_t)inserted_id;
|
||||||
|
db_result = execute(database, "COMMIT", "commit geometric result creation");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (db_result != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)execute(database, "ROLLBACK", "rollback geometric result creation");
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
if (db_result != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return db_result;
|
||||||
|
}
|
||||||
|
result->geometric_verification_result_id = result_id;
|
||||||
|
result->match_result_id = match_result_id;
|
||||||
|
result->verifier_kind = verifier_kind;
|
||||||
|
result->verifier_version = verifier_version;
|
||||||
|
memcpy(result->parameter_fingerprint, parameter_fingerprint,
|
||||||
|
LARDON3D_PROJECT_DB_SHA256_SIZE);
|
||||||
|
result->status = status;
|
||||||
|
result->inlier_count = inlier_count;
|
||||||
|
result->inlier_mask_size = inlier_mask_size;
|
||||||
|
memcpy(result->inlier_mask, inlier_mask, inlier_mask_size);
|
||||||
|
result->has_model = model != NULL;
|
||||||
|
if (model) {
|
||||||
|
memcpy(result->model, model, sizeof(result->model));
|
||||||
|
}
|
||||||
|
result->created_at = created_at;
|
||||||
|
return LARDON3D_PROJECT_DB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verification_result(
|
||||||
|
Lardon3DProjectDb *database, uint64_t geometric_verification_result_id,
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
||||||
|
if (!database || !valid_catalog_id(geometric_verification_result_id) || !result) {
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
memset(result, 0, sizeof(*result));
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
char sql[1024];
|
||||||
|
(void)snprintf(sql, sizeof(sql), "SELECT %sFROM geometric_verification_results g "
|
||||||
|
"JOIN match_results m ON m.match_result_id=g.match_result_id "
|
||||||
|
"WHERE g.geometric_verification_result_id=?1",
|
||||||
|
geometric_result_columns);
|
||||||
|
Lardon3DProjectDbResult db_result = prepare(database, sql, &statement);
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)geometric_verification_result_id);
|
||||||
|
db_result = read_single_geometric_result(database, statement, result);
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
return db_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_find_geometric_verification_result(
|
||||||
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
||||||
|
Lardon3DGeometricVerifierKind verifier_kind, uint32_t verifier_version,
|
||||||
|
const unsigned char parameter_fingerprint[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *result) {
|
||||||
|
if (!database || !valid_catalog_id(match_result_id) ||
|
||||||
|
verifier_kind != LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL || verifier_version == 0 ||
|
||||||
|
!parameter_fingerprint || !result) {
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
memset(result, 0, sizeof(*result));
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
char sql[1280];
|
||||||
|
(void)snprintf(sql, sizeof(sql), "SELECT %sFROM geometric_verification_results g "
|
||||||
|
"JOIN match_results m ON m.match_result_id=g.match_result_id "
|
||||||
|
"WHERE g.match_result_id=?1 AND g.verifier_kind=?2 AND "
|
||||||
|
"g.verifier_version=?3 AND g.parameter_fingerprint=?4",
|
||||||
|
geometric_result_columns);
|
||||||
|
Lardon3DProjectDbResult db_result = prepare(database, sql, &statement);
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
||||||
|
(void)sqlite3_bind_int(statement, 2, (int)verifier_kind);
|
||||||
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)verifier_version);
|
||||||
|
(void)sqlite3_bind_blob(statement, 4, parameter_fingerprint,
|
||||||
|
LARDON3D_PROJECT_DB_SHA256_SIZE, SQLITE_TRANSIENT);
|
||||||
|
db_result = read_single_geometric_result(database, statement, result);
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
return db_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_list_geometric_verification_results(
|
||||||
|
Lardon3DProjectDb *database, uint64_t match_result_id,
|
||||||
|
uint64_t after_geometric_verification_result_id,
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult *results, size_t capacity, size_t *count) {
|
||||||
|
if (count) {
|
||||||
|
*count = 0;
|
||||||
|
}
|
||||||
|
if (!database || !valid_catalog_id(match_result_id) ||
|
||||||
|
after_geometric_verification_result_id > INT64_MAX || !results || !count || capacity == 0 ||
|
||||||
|
capacity > LARDON3D_PROJECT_DB_GEOMETRIC_RESULT_PAGE_MAX) {
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
char sql[1280];
|
||||||
|
(void)snprintf(sql, sizeof(sql), "SELECT %sFROM geometric_verification_results g "
|
||||||
|
"JOIN match_results m ON m.match_result_id=g.match_result_id "
|
||||||
|
"WHERE g.match_result_id=?1 AND "
|
||||||
|
"g.geometric_verification_result_id>?2 "
|
||||||
|
"ORDER BY g.geometric_verification_result_id LIMIT ?3",
|
||||||
|
geometric_result_columns);
|
||||||
|
Lardon3DProjectDbResult db_result = prepare(database, sql, &statement);
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
(void)sqlite3_bind_int64(statement, 1, (sqlite3_int64)match_result_id);
|
||||||
|
(void)sqlite3_bind_int64(statement, 2,
|
||||||
|
(sqlite3_int64)after_geometric_verification_result_id);
|
||||||
|
(void)sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
||||||
|
int code = SQLITE_DONE;
|
||||||
|
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
||||||
|
if (!read_geometric_result(statement, &results[*count])) {
|
||||||
|
db_result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++*count;
|
||||||
|
}
|
||||||
|
if (db_result == LARDON3D_PROJECT_DB_OK && *count < capacity && code != SQLITE_DONE) {
|
||||||
|
db_result = sqlite_result(database, code, "list geometric results");
|
||||||
|
}
|
||||||
|
(void)sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
return db_result;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef LARDON3D_PROJECT_DB_TESTING
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_test_delete_feature_identity(
|
Lardon3DProjectDbResult lardon3d_project_db_test_delete_feature_identity(
|
||||||
Lardon3DProjectDb *database, uint64_t feature_set_id, uint64_t feature_asset_id) {
|
Lardon3DProjectDb *database, uint64_t feature_set_id, uint64_t feature_asset_id) {
|
||||||
|
|
|
||||||
463
tests/test_geometric_verification.c
Normal file
463
tests/test_geometric_verification.c
Normal file
|
|
@ -0,0 +1,463 @@
|
||||||
|
#include <math.h>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <lardon3d/project_db.h>
|
||||||
|
|
||||||
|
#define CHECK(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t matched_id;
|
||||||
|
uint64_t no_match_id;
|
||||||
|
uint64_t second_matched_id;
|
||||||
|
uint64_t candidate_pair_id;
|
||||||
|
uint64_t feature_set_id_a;
|
||||||
|
uint64_t feature_set_id_b;
|
||||||
|
} Parents;
|
||||||
|
|
||||||
|
static bool execute_sql(const char *path, const char *sql) {
|
||||||
|
sqlite3 *connection = NULL;
|
||||||
|
if (sqlite3_open(path, &connection) != SQLITE_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ok = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
|
||||||
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool query_integer(const char *path, const char *sql, sqlite3_int64 expected) {
|
||||||
|
sqlite3 *connection = NULL;
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
if (sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK ||
|
||||||
|
sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) != SQLITE_OK) {
|
||||||
|
if (statement) (void)sqlite3_finalize(statement);
|
||||||
|
if (connection) (void)sqlite3_close(connection);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool matches = sqlite3_step(statement) == SQLITE_ROW &&
|
||||||
|
sqlite3_column_int64(statement, 0) == expected &&
|
||||||
|
sqlite3_step(statement) == SQLITE_DONE;
|
||||||
|
return sqlite3_finalize(statement) == SQLITE_OK && sqlite3_close(connection) == SQLITE_OK &&
|
||||||
|
matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void asset_path(const unsigned char hash[32], const char *kind,
|
||||||
|
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY]) {
|
||||||
|
static const char digits[] = "0123456789abcdef";
|
||||||
|
char hex[65];
|
||||||
|
for (size_t index = 0; index < 32; ++index) {
|
||||||
|
hex[index * 2] = digits[hash[index] >> 4];
|
||||||
|
hex[index * 2 + 1] = digits[hash[index] & 15U];
|
||||||
|
}
|
||||||
|
hex[64] = '\0';
|
||||||
|
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, "assets/%s/%c%c/%s", kind, hex[0],
|
||||||
|
hex[1], hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool create_match_result(Lardon3DProjectDb *database, uint64_t pair_id, uint64_t fs_a,
|
||||||
|
uint64_t fs_b, uint32_t version, unsigned char fingerprint_byte,
|
||||||
|
int status, uint32_t match_count, uint64_t *id) {
|
||||||
|
unsigned char fingerprint[32] = {0};
|
||||||
|
unsigned char hash[32] = {0};
|
||||||
|
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||||
|
memset(fingerprint, fingerprint_byte, sizeof(fingerprint));
|
||||||
|
memset(hash, (unsigned char)(fingerprint_byte + 64U), sizeof(hash));
|
||||||
|
asset_path(hash, "matches", path);
|
||||||
|
Lardon3DProjectDbMatchResult result;
|
||||||
|
Lardon3DProjectDbResult db_result = lardon3d_project_db_create_match_result(
|
||||||
|
database, pair_id, fs_a, fs_b, "model-test", version, fingerprint, status, match_count,
|
||||||
|
status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? hash : NULL,
|
||||||
|
status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? path : NULL,
|
||||||
|
status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? 32U + (uint64_t)match_count * 12U : 0,
|
||||||
|
version, &result);
|
||||||
|
if (db_result != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*id = result.match_result_id;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool create_parents(Lardon3DProjectDb *database, Parents *parents) {
|
||||||
|
Lardon3DProjectDbScanSet scanset;
|
||||||
|
if (lardon3d_project_db_create_scanset(database, "Geometry-model", &scanset) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unsigned char image_hash_a[32] = {0x11};
|
||||||
|
unsigned char image_hash_b[32] = {0x22};
|
||||||
|
char image_path_a[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||||
|
char image_path_b[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||||
|
asset_path(image_hash_a, "images", image_path_a);
|
||||||
|
asset_path(image_hash_b, "images", image_path_b);
|
||||||
|
Lardon3DProjectDbImageRegisterStatus register_status;
|
||||||
|
Lardon3DProjectDbImage image_a;
|
||||||
|
Lardon3DProjectDbImage image_b;
|
||||||
|
if (lardon3d_project_db_register_image(database, scanset.scanset_id, image_hash_a, image_path_a,
|
||||||
|
10, "a.jpg", "/a.jpg", 0, 1, ®ister_status,
|
||||||
|
&image_a) != LARDON3D_PROJECT_DB_OK ||
|
||||||
|
lardon3d_project_db_register_image(database, scanset.scanset_id, image_hash_b, image_path_b,
|
||||||
|
10, "b.jpg", "/b.jpg", 0, 2, ®ister_status,
|
||||||
|
&image_b) != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Lardon3DProjectDbCandidatePair pair;
|
||||||
|
if (lardon3d_project_db_create_candidate_pair(database, image_a.image_id, image_b.image_id, 3,
|
||||||
|
&pair) != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unsigned char feature_hash_a[32] = {0x33};
|
||||||
|
unsigned char feature_hash_b[32] = {0x44};
|
||||||
|
char feature_path_a[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||||
|
char feature_path_b[LARDON3D_PROJECT_DB_PATH_CAPACITY];
|
||||||
|
asset_path(feature_hash_a, "features", feature_path_a);
|
||||||
|
asset_path(feature_hash_b, "features", feature_path_b);
|
||||||
|
Lardon3DProjectDbFeatureSet feature_a;
|
||||||
|
Lardon3DProjectDbFeatureSet feature_b;
|
||||||
|
if (lardon3d_project_db_register_feature_set(
|
||||||
|
database, image_a.image_id, "orb", 1, feature_hash_a, image_hash_a, 8192, 1, 32,
|
||||||
|
feature_hash_a, feature_path_a, 100, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 4,
|
||||||
|
&feature_a) != LARDON3D_PROJECT_DB_OK ||
|
||||||
|
lardon3d_project_db_register_feature_set(
|
||||||
|
database, image_b.image_id, "orb", 1, feature_hash_b, image_hash_b, 8192, 1, 32,
|
||||||
|
feature_hash_b, feature_path_b, 100, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 5,
|
||||||
|
&feature_b) != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
parents->candidate_pair_id = pair.candidate_pair_id;
|
||||||
|
parents->feature_set_id_a = feature_a.feature_set_id;
|
||||||
|
parents->feature_set_id_b = feature_b.feature_set_id;
|
||||||
|
return create_match_result(database, pair.candidate_pair_id, feature_a.feature_set_id,
|
||||||
|
feature_b.feature_set_id, 1, 0x51,
|
||||||
|
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 9, &parents->matched_id) &&
|
||||||
|
create_match_result(database, pair.candidate_pair_id, feature_a.feature_set_id,
|
||||||
|
feature_b.feature_set_id, 2, 0x52,
|
||||||
|
LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, &parents->no_match_id) &&
|
||||||
|
create_match_result(database, pair.candidate_pair_id, feature_a.feature_set_id,
|
||||||
|
feature_b.feature_set_id, 3, 0x53,
|
||||||
|
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 8192,
|
||||||
|
&parents->second_matched_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool create_v11_database(const char *path) {
|
||||||
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
|
if (lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
return execute_sql(path,
|
||||||
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE geometric_verification_results;"
|
||||||
|
"UPDATE metadata SET value=11 WHERE key='schema_version';"
|
||||||
|
"COMMIT;PRAGMA foreign_keys=ON;");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_model_api(const char *path) {
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
|
Parents parents;
|
||||||
|
CHECK(create_parents(database, &parents));
|
||||||
|
|
||||||
|
unsigned char fingerprint_a[32];
|
||||||
|
unsigned char fingerprint_b[32];
|
||||||
|
memset(fingerprint_a, 0xa1, sizeof(fingerprint_a));
|
||||||
|
memset(fingerprint_b, 0xb2, sizeof(fingerprint_b));
|
||||||
|
unsigned char mask_a[2] = {0x01, 0x00};
|
||||||
|
double model[9] = {0.0, -1.0, 1e-300, 1e300, 4.0, 5.0, 6.0, 7.0, 8.0};
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult verified;
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), model, 10,
|
||||||
|
&verified) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(verified.has_model && verified.inlier_mask_size == 2 &&
|
||||||
|
memcmp(verified.model, model, sizeof(model)) == 0);
|
||||||
|
|
||||||
|
unsigned char rejected_mask[2] = {0x03, 0x00};
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult rejected;
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 2,
|
||||||
|
fingerprint_b, LARDON3D_GEOMETRIC_REJECTED, 2, rejected_mask,
|
||||||
|
sizeof(rejected_mask), NULL, 11, &rejected) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(!rejected.has_model && rejected.inlier_count == 2);
|
||||||
|
|
||||||
|
unsigned char zero_mask[2] = {0};
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult zero_inliers;
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3,
|
||||||
|
fingerprint_b, LARDON3D_GEOMETRIC_REJECTED, 0, zero_mask, sizeof(zero_mask), NULL, 11,
|
||||||
|
&zero_inliers) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult loaded;
|
||||||
|
CHECK(lardon3d_project_db_load_geometric_verification_result(
|
||||||
|
database, verified.geometric_verification_result_id, &loaded) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(loaded.match_result_id == parents.matched_id && loaded.has_model &&
|
||||||
|
memcmp(loaded.model, model, sizeof(model)) == 0 &&
|
||||||
|
memcmp(loaded.inlier_mask, mask_a, sizeof(mask_a)) == 0);
|
||||||
|
CHECK(lardon3d_project_db_find_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
|
||||||
|
fingerprint_a, &loaded) == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
loaded.geometric_verification_result_id == verified.geometric_verification_result_id);
|
||||||
|
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 2, rejected_mask,
|
||||||
|
sizeof(rejected_mask), NULL, 12, &loaded) == LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.no_match_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 0, mask_a, 1, NULL, 12,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, UINT64_C(9223372036854775807),
|
||||||
|
LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, fingerprint_a,
|
||||||
|
LARDON3D_GEOMETRIC_REJECTED, 0, mask_a, 1, NULL, 12,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||||
|
|
||||||
|
unsigned char bad_padding[2] = {0x01, 0x80};
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 1, bad_padding, sizeof(bad_padding),
|
||||||
|
NULL, 12, &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 2, mask_a, sizeof(mask_a), NULL, 12,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
unsigned char all_inliers[2] = {0xff, 0x01};
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 4,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 9, all_inliers, sizeof(all_inliers), NULL,
|
||||||
|
12, &loaded) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 5,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 10, all_inliers, sizeof(all_inliers), NULL,
|
||||||
|
12, &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), NULL, 12,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 1, mask_a, sizeof(mask_a), model, 12,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
double bad_model[9] = {0};
|
||||||
|
bad_model[4] = NAN;
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), bad_model, 12,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
bad_model[4] = INFINITY;
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), bad_model, 12,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
|
||||||
|
unsigned char max_mask[LARDON3D_PROJECT_DB_INLIER_MASK_MAX] = {0};
|
||||||
|
max_mask[0] = 0x01;
|
||||||
|
max_mask[1023] = 0x80;
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.second_matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
|
||||||
|
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 2, max_mask, sizeof(max_mask), NULL, 13,
|
||||||
|
&loaded) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(loaded.inlier_mask_size == 1024 && loaded.inlier_count == 2);
|
||||||
|
|
||||||
|
static const uint32_t boundary_counts[] = {1, 2, 7, 8, 9, 63, 64, 65, 8191};
|
||||||
|
for (size_t boundary_index = 0;
|
||||||
|
boundary_index < sizeof(boundary_counts) / sizeof(boundary_counts[0]); ++boundary_index) {
|
||||||
|
uint32_t match_count = boundary_counts[boundary_index];
|
||||||
|
uint64_t parent_id = 0;
|
||||||
|
CHECK(create_match_result(database, parents.candidate_pair_id, parents.feature_set_id_a,
|
||||||
|
parents.feature_set_id_b, (uint32_t)(10U + boundary_index),
|
||||||
|
(unsigned char)(0x60U + boundary_index),
|
||||||
|
LARDON3D_MATCH_RESULT_STATUS_MATCHED, match_count, &parent_id));
|
||||||
|
unsigned char boundary_mask[LARDON3D_PROJECT_DB_INLIER_MASK_MAX] = {0};
|
||||||
|
size_t boundary_size = ((size_t)match_count + 7U) / 8U;
|
||||||
|
uint32_t last_index = match_count - 1U;
|
||||||
|
boundary_mask[last_index / 8U] = (unsigned char)(1U << (last_index % 8U));
|
||||||
|
unsigned char boundary_fingerprint[32];
|
||||||
|
memset(boundary_fingerprint, (int)(0xc0U + boundary_index),
|
||||||
|
sizeof(boundary_fingerprint));
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parent_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
|
||||||
|
boundary_fingerprint, LARDON3D_GEOMETRIC_REJECTED, 1, boundary_mask,
|
||||||
|
boundary_size, NULL, 20 + (int64_t)boundary_index, &loaded) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(loaded.inlier_mask_size == boundary_size &&
|
||||||
|
loaded.inlier_mask[last_index / 8U] == (unsigned char)(1U << (last_index % 8U)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult page[2];
|
||||||
|
size_t count = 0;
|
||||||
|
CHECK(lardon3d_project_db_list_geometric_verification_results(
|
||||||
|
database, parents.matched_id, 0, page, 1, &count) == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
count == 1);
|
||||||
|
uint64_t cursor = page[0].geometric_verification_result_id;
|
||||||
|
CHECK(lardon3d_project_db_list_geometric_verification_results(
|
||||||
|
database, parents.matched_id, cursor, page, 2, &count) == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
count == 2 && page[0].geometric_verification_result_id > cursor);
|
||||||
|
CHECK(lardon3d_project_db_list_geometric_verification_results(
|
||||||
|
database, parents.matched_id, 0, page,
|
||||||
|
LARDON3D_PROJECT_DB_GEOMETRIC_RESULT_PAGE_MAX + 1U, &count) ==
|
||||||
|
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
|
||||||
|
uint64_t verified_id = verified.geometric_verification_result_id;
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
database = NULL;
|
||||||
|
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_load_geometric_verification_result(database, verified_id, &loaded) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK && memcmp(loaded.model, model, sizeof(model)) == 0);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool loader_rejects_after_sql(const char *path, const char *sql) {
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
if (!execute_sql(path, sql) ||
|
||||||
|
lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult result;
|
||||||
|
bool rejected = lardon3d_project_db_load_geometric_verification_result(database, 1, &result) ==
|
||||||
|
LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
return rejected;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_corruption(const char *path) {
|
||||||
|
static const char reset[] =
|
||||||
|
"PRAGMA ignore_check_constraints=ON;"
|
||||||
|
"UPDATE geometric_verification_results SET verifier_kind=1,verifier_version=1,"
|
||||||
|
"parameter_fingerprint=zeroblob(32),status=2,inlier_count=1,inlier_mask=x'0100',"
|
||||||
|
"model_m00=0,model_m01=0,model_m02=0,model_m10=0,model_m11=0,model_m12=0,"
|
||||||
|
"model_m20=0,model_m21=0,model_m22=0 WHERE geometric_verification_result_id=1;";
|
||||||
|
CHECK(execute_sql(path, reset));
|
||||||
|
|
||||||
|
static const char *corruptions[] = {
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET verifier_kind=9 WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET verifier_kind=1,verifier_version=0 WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET verifier_version=1,parameter_fingerprint=x'01' "
|
||||||
|
"WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET parameter_fingerprint=zeroblob(32),status=9 "
|
||||||
|
"WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET status=2,inlier_count=9000 WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET inlier_count=1,inlier_mask=x'03' WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET inlier_mask=x'0180' WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET inlier_mask=x'0300' WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET inlier_mask=x'0100',model_m00=NULL WHERE geometric_verification_result_id=1;",
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results "
|
||||||
|
"SET model_m00=1e999 WHERE geometric_verification_result_id=1;",
|
||||||
|
};
|
||||||
|
for (size_t index = 0; index < sizeof(corruptions) / sizeof(corruptions[0]); ++index) {
|
||||||
|
CHECK(execute_sql(path, reset));
|
||||||
|
CHECK(loader_rejects_after_sql(path, corruptions[index]));
|
||||||
|
}
|
||||||
|
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
CHECK(execute_sql(path, reset));
|
||||||
|
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult result;
|
||||||
|
CHECK(lardon3d_project_db_load_geometric_verification_result(database, 1, &result) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
uint64_t parent_id = result.match_result_id;
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
char parent_corruption[512];
|
||||||
|
CHECK(snprintf(parent_corruption, sizeof(parent_corruption),
|
||||||
|
"PRAGMA ignore_check_constraints=ON;UPDATE match_results SET result_status=0 "
|
||||||
|
"WHERE match_result_id=%llu;",
|
||||||
|
(unsigned long long)parent_id) > 0);
|
||||||
|
CHECK(loader_rejects_after_sql(path, parent_corruption));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
CHECK(create_v11_database(v11_path));
|
||||||
|
CHECK(query_integer(v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
||||||
|
CHECK(query_integer(v11_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
"name='geometric_verification_results'",
|
||||||
|
0));
|
||||||
|
CHECK(lardon3d_project_db_open(v11_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
|
Parents parents;
|
||||||
|
CHECK(create_parents(database, &parents));
|
||||||
|
unsigned char fingerprint[32] = {0x91};
|
||||||
|
unsigned char mask[2] = {0x01, 0x00};
|
||||||
|
Lardon3DProjectDbGeometricVerificationResult migrated_result;
|
||||||
|
CHECK(lardon3d_project_db_create_geometric_verification_result(
|
||||||
|
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
|
||||||
|
fingerprint, LARDON3D_GEOMETRIC_REJECTED, 1, mask, sizeof(mask), NULL, 30,
|
||||||
|
&migrated_result) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
uint64_t migrated_result_id = migrated_result.geometric_verification_result_id;
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
CHECK(query_integer(v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
|
CHECK(query_integer(v11_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
|
||||||
|
"name='geometric_verification_results_parent_idx'",
|
||||||
|
1));
|
||||||
|
CHECK(lardon3d_project_db_open(v11_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_load_geometric_verification_result(
|
||||||
|
database, migrated_result_id, &migrated_result) == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
migrated_result.match_result_id == parents.matched_id);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
|
||||||
|
CHECK(create_v11_database(failed_path));
|
||||||
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V12", "1", 1) == 0);
|
||||||
|
CHECK(lardon3d_project_db_open(failed_path, &database, error) == LARDON3D_PROJECT_DB_IO_ERROR);
|
||||||
|
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V12") == 0);
|
||||||
|
CHECK(query_integer(failed_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
||||||
|
CHECK(query_integer(failed_path,
|
||||||
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
"name='geometric_verification_results'",
|
||||||
|
0));
|
||||||
|
CHECK(lardon3d_project_db_open(failed_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool run_test(void) {
|
||||||
|
char directory[] = "/tmp/lardon3d-geometric-model-XXXXXX";
|
||||||
|
CHECK(mkdtemp(directory));
|
||||||
|
char database_path[512];
|
||||||
|
char v11_path[512];
|
||||||
|
char failed_path[512];
|
||||||
|
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db", directory) > 0);
|
||||||
|
CHECK(snprintf(v11_path, sizeof(v11_path), "%s/v11.db", directory) > 0);
|
||||||
|
CHECK(snprintf(failed_path, sizeof(failed_path), "%s/failed-v12.db", directory) > 0);
|
||||||
|
CHECK(test_model_api(database_path));
|
||||||
|
CHECK(test_corruption(database_path));
|
||||||
|
CHECK(test_migration(v11_path, failed_path));
|
||||||
|
CHECK(unlink(database_path) == 0);
|
||||||
|
CHECK(unlink(v11_path) == 0);
|
||||||
|
CHECK(unlink(failed_path) == 0);
|
||||||
|
CHECK(rmdir(directory) == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,7 @@ static bool create_v9_database(const char *path) {
|
||||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE geometric_verification_results;"
|
||||||
"DROP TABLE matcher_tasks;"
|
"DROP TABLE matcher_tasks;"
|
||||||
"DROP TABLE match_results;"
|
"DROP TABLE match_results;"
|
||||||
"UPDATE metadata SET value=9 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
"UPDATE metadata SET value=9 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
|
|
@ -93,7 +94,7 @@ static bool run_test(void) {
|
||||||
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) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 11);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 12);
|
||||||
|
|
||||||
Lardon3DProjectDbScanSet scanset;
|
Lardon3DProjectDbScanSet scanset;
|
||||||
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
||||||
|
|
@ -452,7 +453,7 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 11);
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
|
|
||||||
/* 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) ==
|
||||||
|
|
@ -492,10 +493,10 @@ static bool run_test(void) {
|
||||||
CHECK(create_v9_database(v9_path));
|
CHECK(create_v9_database(v9_path));
|
||||||
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
||||||
CHECK(lardon3d_project_db_open(v9_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(v9_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 11);
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
CHECK(query_integer(v9_path,
|
CHECK(query_integer(v9_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='match_results'", 1));
|
"name='match_results'", 1));
|
||||||
|
|
@ -514,7 +515,7 @@ static bool run_test(void) {
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='matcher_tasks'", 0));
|
"name='matcher_tasks'", 0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 11);
|
lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ 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 geometric_verification_results;"
|
||||||
"DROP TABLE matcher_tasks;"
|
"DROP TABLE matcher_tasks;"
|
||||||
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
||||||
"COMMIT;PRAGMA foreign_keys=ON;";
|
"COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
|
|
@ -350,7 +351,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) == 11);
|
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 12);
|
||||||
CHECK(query_integer(database_path,
|
CHECK(query_integer(database_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ static bool create_future_database(const char *path) {
|
||||||
}
|
}
|
||||||
bool ok = sqlite3_exec(connection,
|
bool ok = sqlite3_exec(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',12);",
|
"INSERT INTO metadata VALUES('schema_version',13);",
|
||||||
NULL, NULL, NULL) == SQLITE_OK;
|
NULL, NULL, NULL) == SQLITE_OK;
|
||||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +103,7 @@ static bool create_v7_database(const char *path) {
|
||||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE geometric_verification_results;"
|
||||||
"DROP TABLE matcher_tasks;"
|
"DROP TABLE matcher_tasks;"
|
||||||
"DROP TABLE match_results;"
|
"DROP TABLE match_results;"
|
||||||
"DROP TABLE candidate_pair_generate_tasks;"
|
"DROP TABLE candidate_pair_generate_tasks;"
|
||||||
|
|
@ -121,6 +122,7 @@ static bool create_v6_database(const char *path) {
|
||||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE geometric_verification_results;"
|
||||||
"DROP TABLE matcher_tasks;"
|
"DROP TABLE matcher_tasks;"
|
||||||
"DROP TABLE match_results;"
|
"DROP TABLE match_results;"
|
||||||
"DROP TABLE candidate_pair_generate_tasks;"
|
"DROP TABLE candidate_pair_generate_tasks;"
|
||||||
|
|
@ -149,6 +151,7 @@ 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 geometric_verification_results;"
|
||||||
"DROP TABLE matcher_tasks;"
|
"DROP TABLE matcher_tasks;"
|
||||||
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
||||||
"COMMIT;PRAGMA foreign_keys=ON;";
|
"COMMIT;PRAGMA foreign_keys=ON;";
|
||||||
|
|
@ -335,7 +338,7 @@ static bool run_test(void) {
|
||||||
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) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 11);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 12);
|
||||||
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 &&
|
||||||
|
|
@ -602,7 +605,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
lardon3d_project_db_close(contexts[0].database);
|
lardon3d_project_db_close(contexts[0].database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(database_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
CHECK(query_integer(database_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
CHECK(query_integer(database_path, "SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
CHECK(query_integer(database_path, "SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 1, &task) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_load_task(database, 1, &task) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
@ -624,7 +627,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) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(legacy_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 11);
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
CHECK(lardon3d_project_db_get_project(database, &loaded_project) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_get_project(database, &loaded_project) == LARDON3D_PROJECT_DB_OK &&
|
||||||
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
@ -634,7 +637,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(legacy_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
CHECK(query_integer(legacy_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -659,7 +662,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
CHECK(query_integer(v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -687,7 +690,7 @@ static bool run_test(void) {
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
CHECK(query_integer(v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -704,7 +707,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) == 11);
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_load_task(database, 9, &task) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact", &loaded_artifact) ==
|
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact", &loaded_artifact) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
@ -743,24 +746,24 @@ static bool run_test(void) {
|
||||||
fprintf(stderr, "Nouvelle tentative migration v7 (%d): %s\n", (int)retry_v7, error);
|
fprintf(stderr, "Nouvelle tentative migration v7 (%d): %s\n", (int)retry_v7, error);
|
||||||
}
|
}
|
||||||
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 11);
|
lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
CHECK(create_v5_database(direct_v5_path));
|
CHECK(create_v5_database(direct_v5_path));
|
||||||
CHECK(query_integer(direct_v5_path, "SELECT value FROM metadata WHERE key='schema_version'", 5));
|
CHECK(query_integer(direct_v5_path, "SELECT value FROM metadata WHERE key='schema_version'", 5));
|
||||||
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 11);
|
lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
CHECK(create_v7_database(v8_path));
|
CHECK(create_v7_database(v8_path));
|
||||||
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
||||||
CHECK(lardon3d_project_db_open(v8_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(v8_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 11);
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
CHECK(query_integer(v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
|
|
||||||
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);
|
||||||
|
|
@ -771,7 +774,7 @@ static bool run_test(void) {
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='candidate_pairs'", 0));
|
"name='candidate_pairs'", 0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 11);
|
lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -786,10 +789,10 @@ static bool run_test(void) {
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
CHECK(lardon3d_project_db_open(v10_path, &database, error) == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 11);
|
lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
|
CHECK(query_integer(v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 12));
|
||||||
CHECK(query_integer(v10_path,
|
CHECK(query_integer(v10_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
|
|
@ -812,7 +815,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) == 11);
|
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue