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
|
||||
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences
|
||||
- **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
|
||||
- **Recovery projet** : reprise automatique sélective et bornée des imports récupérables
|
||||
- **Task Queue** : file FIFO avec sélection adaptative et backpressure
|
||||
|
|
@ -111,6 +112,7 @@ Acquisitions
|
|||
- [Candidate Pair](docs/architecture/candidate_pair.md)
|
||||
- [Match Result](docs/architecture/match_result.md)
|
||||
- [Matcher](docs/architecture/matcher.md)
|
||||
- [Geometric Verification](docs/architecture/geometric_verification.md)
|
||||
- [Backend Vulkan ORB](docs/architecture/vulkan_matcher.md)
|
||||
- [Viewer](docs/architecture/viewer.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,
|
||||
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
|
||||
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,
|
||||
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
|
||||
|
||||
|
|
|
|||
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
|
||||
é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
|
||||
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
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
> 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
|
||||
> uniquement `match_results` pour le Match Result Model. La migration v8→v9
|
||||
> 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_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
|
||||
|
||||
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
|
||||
`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
|
||||
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
|
||||
`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
|
||||
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` :
|
||||
|
||||
|
|
@ -545,8 +592,8 @@ ouvert.
|
|||
|
||||
## Statut
|
||||
|
||||
**IMPLEMENTED** — SQLite système, schéma v11 et migrations séquentielles
|
||||
v1→v2→v3→v4→v5→v6→v7→v8→v9→v10→v11, identité projet, transactions
|
||||
**IMPLEMENTED** — SQLite système, schéma v12 et migrations séquentielles
|
||||
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.
|
||||
|
||||
**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. |
|
||||
|
||||
**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`
|
||||
à 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
|
||||
|
|
@ -229,7 +239,8 @@ Image Catalog (B) ──► Feature Store (C)
|
|||
## Statut du pipeline
|
||||
|
||||
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**.
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
#include <lardon3d/task.h>
|
||||
|
||||
enum {
|
||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 11,
|
||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 12,
|
||||
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
||||
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
||||
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
||||
|
|
@ -18,6 +18,9 @@ enum {
|
|||
LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX = 256,
|
||||
LARDON3D_PROJECT_DB_CANDIDATE_PAIR_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_IMAGE_NAME_CAPACITY = 256,
|
||||
LARDON3D_PROJECT_DB_SHA256_SIZE = 32,
|
||||
|
|
@ -158,6 +161,30 @@ typedef struct {
|
|||
int64_t created_at;
|
||||
} 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 {
|
||||
LARDON3D_PROJECT_DB_IMAGE_REGISTERED = 0,
|
||||
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,
|
||||
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
|
||||
|
|
|
|||
16
meson.build
16
meson.build
|
|
@ -507,6 +507,22 @@ match_result_test = executable(
|
|||
|
||||
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(
|
||||
'test-project',
|
||||
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),"
|
||||
"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) {
|
||||
if (destination) {
|
||||
(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 &&
|
||||
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;
|
||||
}
|
||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
||||
|
|
@ -568,6 +593,22 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
|||
"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) {
|
||||
result = execute(database, "COMMIT", "commit migration");
|
||||
}
|
||||
|
|
@ -682,7 +723,8 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
|
|||
"feature_support_members",
|
||||
"candidate_pairs",
|
||||
"matcher_tasks",
|
||||
"match_results"};
|
||||
"match_results",
|
||||
"geometric_verification_results"};
|
||||
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
|
||||
result == LARDON3D_PROJECT_DB_OK;
|
||||
++index) {
|
||||
|
|
@ -3751,6 +3793,366 @@ Lardon3DProjectDbResult lardon3d_project_db_list_match_results(
|
|||
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
|
||||
Lardon3DProjectDbResult lardon3d_project_db_test_delete_feature_identity(
|
||||
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;
|
||||
static const char sql[] =
|
||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||
"DROP TABLE geometric_verification_results;"
|
||||
"DROP TABLE matcher_tasks;"
|
||||
"DROP TABLE match_results;"
|
||||
"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];
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
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;
|
||||
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
||||
|
|
@ -452,7 +453,7 @@ static bool run_test(void) {
|
|||
database = NULL;
|
||||
|
||||
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 */
|
||||
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(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_schema_version(database) == 11);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||
lardon3d_project_db_close(database);
|
||||
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,
|
||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||
"name='match_results'", 1));
|
||||
|
|
@ -514,7 +515,7 @@ static bool run_test(void) {
|
|||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||
"name='matcher_tasks'", 0));
|
||||
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);
|
||||
database = NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
|
|||
}
|
||||
static const char sql[] =
|
||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||
"DROP TABLE geometric_verification_results;"
|
||||
"DROP TABLE matcher_tasks;"
|
||||
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
||||
"COMMIT;PRAGMA foreign_keys=ON;";
|
||||
|
|
@ -350,7 +351,7 @@ static bool run_test(void) {
|
|||
"name='matcher_tasks'",
|
||||
0));
|
||||
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,
|
||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||
"name='matcher_tasks'",
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ static bool create_future_database(const char *path) {
|
|||
}
|
||||
bool ok = sqlite3_exec(connection,
|
||||
"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;
|
||||
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;
|
||||
static const char sql[] =
|
||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||
"DROP TABLE geometric_verification_results;"
|
||||
"DROP TABLE matcher_tasks;"
|
||||
"DROP TABLE match_results;"
|
||||
"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;
|
||||
static const char sql[] =
|
||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||
"DROP TABLE geometric_verification_results;"
|
||||
"DROP TABLE matcher_tasks;"
|
||||
"DROP TABLE match_results;"
|
||||
"DROP TABLE candidate_pair_generate_tasks;"
|
||||
|
|
@ -149,6 +151,7 @@ static bool create_v10_database(const char *path) {
|
|||
}
|
||||
static const char sql[] =
|
||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||
"DROP TABLE geometric_verification_results;"
|
||||
"DROP TABLE matcher_tasks;"
|
||||
"UPDATE metadata SET value=10 WHERE key='schema_version';"
|
||||
"COMMIT;PRAGMA foreign_keys=ON;";
|
||||
|
|
@ -335,7 +338,7 @@ static bool run_test(void) {
|
|||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
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;
|
||||
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
|
|
@ -602,7 +605,7 @@ static bool run_test(void) {
|
|||
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
lardon3d_project_db_close(contexts[0].database);
|
||||
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(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);
|
||||
|
|
@ -624,7 +627,7 @@ static bool run_test(void) {
|
|||
|
||||
CHECK(create_v1_database(legacy_path));
|
||||
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 &&
|
||||
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
||||
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_close(database);
|
||||
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(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_close(database);
|
||||
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(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_close(database);
|
||||
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(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);
|
||||
}
|
||||
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_artifact(database, "legacy-artifact", &loaded_artifact) ==
|
||||
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);
|
||||
}
|
||||
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);
|
||||
database = NULL;
|
||||
|
||||
CHECK(create_v5_database(direct_v5_path));
|
||||
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 &&
|
||||
lardon3d_project_db_schema_version(database) == 11);
|
||||
lardon3d_project_db_schema_version(database) == 12);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
CHECK(create_v7_database(v8_path));
|
||||
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_schema_version(database) == 11);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 12);
|
||||
lardon3d_project_db_close(database);
|
||||
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(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 "
|
||||
"name='candidate_pairs'", 0));
|
||||
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);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -786,10 +789,10 @@ static bool run_test(void) {
|
|||
"name='matcher_tasks'",
|
||||
0));
|
||||
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);
|
||||
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,
|
||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||
"name='matcher_tasks'",
|
||||
|
|
@ -812,7 +815,7 @@ static bool run_test(void) {
|
|||
1));
|
||||
CHECK(lardon3d_project_db_open(failed_v11_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);
|
||||
database = NULL;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue