feat(reconstruction): freeze sparse SfM persistence model v1

This commit is contained in:
fy59 2026-08-10 11:47:41 +02:00
parent d91b25fafd
commit 95700de339
18 changed files with 5479 additions and 974 deletions

View file

@ -119,7 +119,8 @@ l'extraction après import et planification multi-image/DAG.
les Feature Sets persistés. les Feature Sets persistés.
**IMPLEMENTED** — vérification géométrique et tracks. **Sparse SfM Gate A **IMPLEMENTED** — vérification géométrique et tracks. **Sparse SfM Gate A
PASS**, production Sparse SfM remains **NOT_IMPLEMENTED** until its later PASS**, the Sparse SfM v16 persistence model is **FROZEN** after Gate B;
the numerical solver and reconstruction pipeline remain **PLANNED** until later
implementation gates. implementation gates.
## Extension v2 multi-descriptor ## Extension v2 multi-descriptor

View file

@ -1,6 +1,8 @@
# Base de données projet Lardon3D # Base de données projet Lardon3D
> Version courante : **v15**. La migration transactionnelle v14→v15 ajoute > Version courante : **v16**. La migration transactionnelle v15→v16 ajoute
> le modèle persistant Sparse SfM (calibrations, scopes et reconstructions).
> La migration transactionnelle v14→v15 ajoute
> `track_builder_tasks` pour le payload durable explicite du Task Builder. La > `track_builder_tasks` pour le payload durable explicite du Task Builder. La
> migration transactionnelle v13→v14 ajoute > migration transactionnelle v13→v14 ajoute
> les tables `track_sets`, `tracks` et `track_observations` pour le Track > les tables `track_sets`, `tracks` et `track_observations` pour le Track
@ -20,6 +22,21 @@
La base de données projet stocke les métadonnées de reconstruction et les relations entre les La base de données projet stocke les métadonnées de reconstruction et les relations entre les
entités. Elle est conçue pour être légère, persistante et permettre la reprise après interruption. entités. Elle est conçue pour être légère, persistante et permettre la reprise après interruption.
## Sparse SfM Gate B (v16)
Le modèle Sparse SfM v1 est publié atomiquement dans les tables
`sparse_calibrations`, `sparse_calibration_scopes`,
`sparse_calibration_scope_images`, `sparse_reconstructions`,
`sparse_reconstruction_components`, `sparse_registered_images`,
`sparse_landmarks` et `sparse_landmark_observations`. Les résultats sont
immuables et les collections volumineuses sont lues par curseurs bornés ; la
base ne charge jamais une reconstruction complète par défaut. Les coordonnées
restent dans le repère arbitraire de chaque composant et les références
d'observation ne dupliquent ni descripteurs ni coordonnées de pixels.
Les relations de suppression disposent d'index enfants dédiés, notamment sur
`calibration_id` dans les membres de scope et `calibration_scope_id` dans les
reconstructions, afin que les vérifications FK restent indexées.
## Structure conceptuelle ## Structure conceptuelle
### Entités principales ### Entités principales
@ -758,6 +775,12 @@ sont documentés dans `tracks.md`.
**IMPLEMENTED** — Track Builder v1 durable : table `track_builder_tasks`, scope **IMPLEMENTED** — Track Builder v1 durable : table `track_builder_tasks`, scope
asset atomique, payload v1 validé, migration v14→v15 et rollback/retry testés. asset atomique, payload v1 validé, migration v14→v15 et rollback/retry testés.
**IMPLEMENTED** — Sparse SfM v1 persistant : modèle v16 immutable, publication
atomique, composants déterministes, lecteurs bornés, corruption/lifecycle
validation, migration v15→v16 et comparateur fresh/migrated validés par Gate B.
Le modèle de persistance est gelé pour v1 ; le solveur numérique reste hors de
Project DB v16.
**IMPLEMENTED** — API C Track Model v1 : header `project_db.h` et **IMPLEMENTED** — API C Track Model v1 : header `project_db.h` et
source `project_db.c` exposent `create_track_set`, `load_track_set`, source `project_db.c` exposent `create_track_set`, `load_track_set`,
`find_track_set`, `list_track_sets`, `load_track`, `list_tracks`, `find_track_set`, `list_track_sets`, `load_track`, `list_tracks`,

View file

@ -124,13 +124,15 @@ USAC/MAGSAC avec configuration, seed et fingerprint déterministes.
**Statut :** COMPLETED/FROZEN — le Track Builder v1 direct et durable est **Statut :** COMPLETED/FROZEN — le Track Builder v1 direct et durable est
implémenté dans Project DB v15 (`track_sets`, `tracks`, `track_observations` et implémenté dans Project DB v15 (`track_sets`, `tracks`, `track_observations` et
le payload de tâche). La triangulation et le Sparse SfM restent PLANNED. le payload de tâche). La triangulation et le solveur Sparse SfM restent PLANNED;
le modèle de persistance Sparse SfM v16 est gelé après Gate B.
**Sparse SfM Gate A : PASS.** Le contrat géométrique, la stratégie **Sparse SfM Gate A : PASS.** Le contrat géométrique, la stratégie
incremental, la triangulation candidate, le gauge, les conventions de pose, incremental, la triangulation candidate, le gauge, les conventions de pose,
les limites BA et l'enveloppe matérielle sont documentés dans les limites BA et l'enveloppe matérielle sont documentés dans
`architecture/sparse_sfm.md`. La production Sparse SfM reste `architecture/sparse_sfm.md`. Le solveur Sparse SfM reste
**NOT_IMPLEMENTED** jusqu'aux Gates BG. **NOT_IMPLEMENTED** jusqu'aux Gates B3G; sa
persistance v16 et ses lecteurs bornés sont implémentés en B2.
--- ---

View file

@ -106,3 +106,8 @@ une distribution de latence estimator-only.
Le profil maximal explicite et les pools multi-workers restent hors périmètre. Le profil maximal explicite et les pools multi-workers restent hors périmètre.
SIFT/RootSIFT et Feature Extraction Vulkan restent hors de ce contrat. SIFT/RootSIFT et Feature Extraction Vulkan restent hors de ce contrat.
La validation B3 du modèle Sparse SfM v16 a utilisé des processus frais, un
fixture synthétique de 100 000 landmarks et 500 000 observations, cinq passes
de paging sur 50 000 landmarks, et n'a observé ni OOM, ni swap storm, ni dérive
RSS applicative. Cette preuve concerne la persistance bornée, pas le solveur.

View file

@ -3,12 +3,17 @@
## Status and boundary ## Status and boundary
**GATE A — DECISION after contract and probe study.** This document defines the **GATE A — DECISION after contract and probe study.** This document defines the
scientific and architectural contract for the future Sparse SfM layer. It does scientific and architectural contract for the Sparse SfM layer. B2 implements
not implement a solver, change Track Model v1, change Track Builder v1, add a the immutable v16 persistence model and its bounded readers; it does not
Project DB v16, or add a Task Kind. `FACT`, `CANDIDATE`, `DECISION` and `FROZEN` implement a numerical solver, change Track Model v1, change Track Builder v1,
remain explicit: the upstream Track Model and Track Builder are FROZEN; the or add a Task Kind. `FACT`, `CANDIDATE`, `DECISION` and `FROZEN` remain explicit:
choices below are Gate A decisions for the next implementation gates, not a the upstream Track Model and Track Builder are FROZEN, while numerical Sparse
claim that Sparse SfM is implemented. SfM remains deferred to later gates.
**GATE B — PASS.** The v16 persistence model, migration, corruption/lifecycle
proofs, structural comparator, representative resource validation, normal
suite and ASan/UBSan closure are complete. Numerical Sparse SfM remains
deferred to Gate C and later gates.
## Scope ## Scope
@ -383,3 +388,140 @@ the synthetic ground-truth and sparse-solver gates.
No production Sparse SfM, triangulator, camera solver, BA, Project DB v16, No production Sparse SfM, triangulator, camera solver, BA, Project DB v16,
metric alignment, control-point scale, dense/MVS, mesh, texturing, Vulkan SfM, metric alignment, control-point scale, dense/MVS, mesh, texturing, Vulkan SfM,
GPU BA, network/distributed scheduling or UI workflow is implemented here. GPU BA, network/distributed scheduling or UI workflow is implemented here.
## Gate B — model and persistence contract
This section is the **DECISION** contract for the v16 SQL/API work.
It preserves every Gate A decision and supplies only durable vocabulary; no
numeric geometry is introduced.
### Calibration definition and identity
A calibration is an immutable known pinhole model with `width`, `height`,
`fx`, `fy`, `cx`, `cy`, zero skew, `k1`, `k2`, `p1`, and `p2`, all binary64 and
finite. `width` and `height` belong to scientific identity. A calibration's
provenance is an explicit enum (`USER_EXPLICIT` or `IMPORTED_TRUSTED` in v1)
plus a 32-byte provenance fingerprint supplied by the caller. EXIF is never a
calibration origin. Two equal numeric models with different provenance
fingerprints are distinct scientific calibrations because their trust scope is
different; equal content and equal provenance are reused.
The scientific calibration hash is SHA-256 over explicit little-endian fields:
```text
ASCII "L3D3DCP1" (8 bytes)
format_version=1 (uint32)
model_kind (uint32), model_version (uint32)
width (uint32), height (uint32)
fx, fy, cx, cy, k1, k2, p1, p2 (8 canonical binary64 values)
provenance_kind (uint32), provenance_fingerprint (32 bytes)
```
NaN and infinities are rejected. Negative zero is canonicalized to positive
zero before hashing and storage. No native struct, padding or locale text is
serialized. SQLite row IDs remain DB-local references; the hash is the
scientific calibration identity.
### Calibration scope
A scope is immutable and assigns exactly one calibration to each relevant
image. Groups are allowed only when dimensions, crop/orientation coordinate
frame, model/version, numeric parameters and provenance identity are equal;
device or EXIF model names are insufficient. Scope identity is project-local
and content-addressed by SHA-256 over:
```text
ASCII "L3D3DSC1" (8 bytes)
format_version=1 (uint32)
member_count (uint64)
for members sorted by image_id:
image_id (uint64), calibration_hash (32 bytes)
```
The member count is consistency metadata and is also encoded in the digest.
There is no latest-calibration lookup and no silent K rescaling. Feature File
dimensions must match the calibration dimensions exactly.
### Reconstruction identity and components
The immutable reconstruction identity is:
```text
(track_set_id,
calibration_scope_id,
sfm_kind=INCREMENTAL,
sfm_version=1,
parameter_fingerprint[32])
```
`track_set_id` and `calibration_scope_id` are project-local immutable database
references, consistent with the existing Track Model identity convention. The
parameter fingerprint is required to be a 32-byte SHA-256 value but its final
byte encoding remains a later geometry-gate decision. Runtime IDs, timestamps,
worker count, resource state and metrics are excluded.
Component identity is the minimum registered `image_id` in that component. It
is deterministic, project-local, unique because an image belongs to at most one
component, independent of DFS/hash order, and compact. A component always has
at least one registered image. Its coordinates use an independent unit-baseline
gauge and are never comparable to another component without later alignment.
### Persisted model
The minimum v16 model is:
- `sparse_calibrations`: immutable calibration content and hash;
- `sparse_calibration_scopes`: immutable scope hash/member count;
- `sparse_calibration_scope_images`: one image-to-calibration assignment;
- `sparse_reconstructions`: immutable identity and pixel reprojection metrics;
- `sparse_reconstruction_components`: component key and counts;
- `sparse_registered_images`: one world-to-camera pose per image;
- `sparse_landmarks`: one component-local binary64 point per Track;
- `sparse_landmark_observations`: minimal references to the upstream Feature Set
and feature index, without duplicated descriptors or x/y coordinates.
Track ID is globally unique in the existing Track Model table, so one landmark
per reconstruction is uniquely keyed by `(reconstruction_id, track_id)`;
component key remains an attribute/consistency relation rather than redundant
landmark identity. Landmark publication validates that the Track belongs to the
reconstruction's exact Track Set and that every observation belongs to that
Track and its component. Track splitting is impossible in v1.
Observation references are persisted because future BA needs bounded indexed
access from landmark to registered observations without repeatedly reopening
Feature Files. Only `feature_set_id`, `feature_index` and canonical track
position are stored; image ID and x/y remain derivable from immutable upstream
models. This is a deliberate normalization/resource trade-off, not a second
copy of Feature data.
Persisted reprojection metrics use explicit pixel units and names:
`reprojection_rmse_px` and `reprojection_median_px`. They are diagnostics, not
identity. No metric scale or `_mm` field exists.
### Constraints and publication
Publication requires at least two registered images, one component and one
landmark. A result with no usable geometry is rejected rather than represented
by a meaningless empty scientific row. Two-camera reconstruction is valid.
There are no READY/FAILED/PARTIAL scientific states: row existence means a
complete immutable publication. A failed transaction leaves no visible row.
SQL enforces one pose per image, one component per registered image, one
landmark per Track, exact reconstruction uniqueness, scope member uniqueness,
and child foreign keys. The API additionally validates Track Set ownership,
component consistency, calibration coverage, finite values and rotation
orthonormality/determinant. Rotation matrices are never repaired.
The publication transaction inserts the reconstruction and all children using
prepared statements in bounded loops. It contains no Feature File I/O, Track
paging, solver work or Governor wait. Child pages use cursor order and bounded
capacity (64 cameras, 64 landmarks, 64 observations); total scientific counts
have no arbitrary cap beyond checked 64-bit/SQLite limits.
### v16 migration intent
Project DB v15 remains immutable. Gate B adds one transactional v15→v16
migration containing only the eight Sparse SfM model tables and their required
indexes. A true historical v15 fixture, injected rollback, retry, fresh-schema
equivalence and close/reopen are mandatory. No Task, Governor, triangulation,
PnP, BA, GPU or Project DB v17 is introduced.

View file

@ -9,7 +9,7 @@
#include <lardon3d/task.h> #include <lardon3d/task.h>
enum { enum {
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 15, LARDON3D_PROJECT_DB_SCHEMA_VERSION = 16,
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,

View file

@ -0,0 +1,238 @@
#ifndef LARDON3D_SPARSE_SFM_MODEL_H
#define LARDON3D_SPARSE_SFM_MODEL_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <lardon3d/project_db.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
LARDON3D_SPARSE_SFM_SHA256_SIZE = 32,
LARDON3D_SPARSE_SFM_PAGE_MAX = 64,
LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE = 1,
LARDON3D_SPARSE_SFM_CALIBRATION_VERSION = 1,
LARDON3D_SPARSE_SFM_PROVENANCE_USER_EXPLICIT = 1,
LARDON3D_SPARSE_SFM_PROVENANCE_IMPORTED_TRUSTED = 2,
LARDON3D_SPARSE_SFM_KIND_INCREMENTAL = 1,
LARDON3D_SPARSE_SFM_VERSION = 1,
};
typedef struct {
uint64_t calibration_id;
unsigned char scientific_hash[32];
uint32_t model_kind;
uint32_t model_version;
uint32_t width;
uint32_t height;
double fx;
double fy;
double cx;
double cy;
double k1;
double k2;
double p1;
double p2;
uint32_t provenance_kind;
unsigned char provenance_fingerprint[32];
} Lardon3DSparseCalibration;
typedef struct {
uint64_t image_id;
uint64_t calibration_id;
unsigned char calibration_hash[32];
} Lardon3DSparseCalibrationMember;
typedef struct {
uint64_t scope_id;
unsigned char scientific_hash[32];
uint64_t member_count;
} Lardon3DSparseCalibrationScope;
typedef struct {
uint64_t reconstruction_id;
uint64_t track_set_id;
uint64_t calibration_scope_id;
uint32_t sfm_kind;
uint32_t sfm_version;
unsigned char parameter_fingerprint[32];
uint64_t component_count;
uint64_t registered_image_count;
uint64_t landmark_count;
double reprojection_rmse_px;
double reprojection_median_px;
} Lardon3DSparseReconstruction;
typedef struct {
uint64_t component_id;
uint64_t component_key;
uint64_t registered_image_count;
uint64_t landmark_count;
} Lardon3DSparseComponent;
typedef struct {
uint64_t image_id;
uint64_t component_key;
double rotation_cw[9];
double translation_cw[3];
} Lardon3DSparseRegisteredImage;
typedef struct {
uint64_t landmark_id;
uint64_t track_id;
uint64_t component_key;
double x;
double y;
double z;
double reprojection_rmse_px;
double reprojection_median_px;
uint64_t observation_count;
} Lardon3DSparseLandmark;
typedef struct {
uint64_t landmark_id;
uint64_t track_id;
uint64_t feature_set_id;
uint32_t feature_index;
uint32_t position_in_track;
} Lardon3DSparseLandmarkObservation;
typedef struct {
uint64_t track_set_id;
uint64_t calibration_scope_id;
uint32_t sfm_kind;
uint32_t sfm_version;
unsigned char parameter_fingerprint[32];
const Lardon3DSparseComponent *components;
size_t component_count;
const Lardon3DSparseRegisteredImage *registered_images;
size_t registered_image_count;
const Lardon3DSparseLandmark *landmarks;
size_t landmark_count;
const Lardon3DSparseLandmarkObservation *observations;
size_t observation_count;
double reprojection_rmse_px;
double reprojection_median_px;
int64_t created_at;
} Lardon3DSparsePublication;
typedef struct {
uint64_t after_id;
size_t capacity;
size_t count;
uint64_t next_after_id;
Lardon3DSparseCalibration *items;
} Lardon3DSparseCalibrationPage;
typedef struct {
uint64_t after_id;
size_t capacity;
size_t count;
uint64_t next_after_id;
Lardon3DSparseReconstruction *items;
} Lardon3DSparseReconstructionPage;
typedef struct {
uint64_t after_component_key;
size_t capacity;
size_t count;
uint64_t next_component_key;
Lardon3DSparseComponent *items;
} Lardon3DSparseComponentPage;
typedef struct {
uint64_t after_image_id;
size_t capacity;
size_t count;
uint64_t next_image_id;
Lardon3DSparseRegisteredImage *items;
} Lardon3DSparseRegisteredImagePage;
typedef struct {
uint64_t after_track_id;
size_t capacity;
size_t count;
uint64_t next_track_id;
Lardon3DSparseLandmark *items;
} Lardon3DSparseLandmarkPage;
typedef struct {
uint64_t after_landmark_id;
uint32_t after_position_in_track;
size_t capacity;
size_t count;
uint64_t next_landmark_id;
uint32_t next_position_in_track;
Lardon3DSparseLandmarkObservation *items;
} Lardon3DSparseObservationPage;
Lardon3DProjectDbResult
lardon3d_sparse_calibration_create(Lardon3DProjectDb *database,
const Lardon3DSparseCalibration *input,
Lardon3DSparseCalibration *output);
Lardon3DProjectDbResult
lardon3d_sparse_calibration_load(Lardon3DProjectDb *database,
uint64_t calibration_id,
Lardon3DSparseCalibration *output);
Lardon3DProjectDbResult lardon3d_sparse_calibration_find_by_hash(
Lardon3DProjectDb *database, const unsigned char scientific_hash[32],
Lardon3DSparseCalibration *output);
Lardon3DProjectDbResult lardon3d_sparse_calibration_scope_create(
Lardon3DProjectDb *database, const Lardon3DSparseCalibrationMember *members,
size_t member_count, Lardon3DSparseCalibrationScope *output);
Lardon3DProjectDbResult
lardon3d_sparse_calibration_scope_load(Lardon3DProjectDb *database,
uint64_t scope_id,
Lardon3DSparseCalibrationScope *output);
Lardon3DProjectDbResult lardon3d_sparse_calibration_scope_find_by_hash(
Lardon3DProjectDb *database, const unsigned char scientific_hash[32],
Lardon3DSparseCalibrationScope *output);
Lardon3DProjectDbResult lardon3d_sparse_calibration_scope_list_members(
Lardon3DProjectDb *database, uint64_t scope_id, uint64_t after_image_id,
Lardon3DSparseCalibrationMember *items, size_t capacity, size_t *count,
uint64_t *next_after_image_id);
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
Lardon3DProjectDb *database, const Lardon3DSparsePublication *publication,
Lardon3DSparseReconstruction *output);
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_find_exact(
Lardon3DProjectDb *database, uint64_t track_set_id,
uint64_t calibration_scope_id, uint32_t sfm_kind, uint32_t sfm_version,
const unsigned char parameter_fingerprint[32],
Lardon3DSparseReconstruction *output);
Lardon3DProjectDbResult
lardon3d_sparse_reconstruction_load(Lardon3DProjectDb *database,
uint64_t reconstruction_id,
Lardon3DSparseReconstruction *output);
Lardon3DProjectDbResult
lardon3d_sparse_reconstruction_list(Lardon3DProjectDb *database,
uint64_t after_id, size_t capacity,
Lardon3DSparseReconstructionPage *page);
Lardon3DProjectDbResult
lardon3d_sparse_component_list(Lardon3DProjectDb *database,
uint64_t reconstruction_id,
uint64_t after_component_key, size_t capacity,
Lardon3DSparseComponentPage *page);
Lardon3DProjectDbResult
lardon3d_sparse_registered_image_list(Lardon3DProjectDb *database,
uint64_t reconstruction_id,
uint64_t after_image_id, size_t capacity,
Lardon3DSparseRegisteredImagePage *page);
Lardon3DProjectDbResult lardon3d_sparse_landmark_list(
Lardon3DProjectDb *database, uint64_t reconstruction_id,
uint64_t after_track_id, size_t capacity, Lardon3DSparseLandmarkPage *page);
Lardon3DProjectDbResult lardon3d_sparse_observation_list(
Lardon3DProjectDb *database, uint64_t reconstruction_id,
uint64_t after_landmark_id, uint32_t after_position_in_track,
size_t capacity, Lardon3DSparseObservationPage *page);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -143,7 +143,7 @@ executable(
'src/geometric_verifier_task.c', 'src/geometric_verifier_task.c',
'src/image_view.c', 'src/image_view.c',
'src/project.c', 'src/project.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/task_checkpoint.c', 'src/task_checkpoint.c',
'src/task_kind_registry.c', 'src/task_kind_registry.c',
@ -172,7 +172,7 @@ import_test = executable(
'src/app_state.c', 'src/app_state.c',
'src/import.c', 'src/import.c',
'src/image_catalog_persistent.c', 'src/image_catalog_persistent.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -191,7 +191,7 @@ import_task_test = executable(
'src/import.c', 'src/import.c',
'src/import_task.c', 'src/import_task.c',
'src/project.c', 'src/project.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/task_checkpoint.c', 'src/task_checkpoint.c',
'src/task_kind_registry.c', 'src/task_kind_registry.c',
@ -229,7 +229,7 @@ image_catalog_test = executable(
'src/app_state.c', 'src/app_state.c',
'src/image_catalog.c', 'src/image_catalog.c',
'src/image_catalog_persistent.c', 'src/image_catalog_persistent.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -246,7 +246,7 @@ persistent_image_catalog_test = executable(
'tests/test_persistent_image_catalog.c', 'tests/test_persistent_image_catalog.c',
'src/app_state.c', 'src/app_state.c',
'src/image_catalog_persistent.c', 'src/image_catalog_persistent.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -266,7 +266,7 @@ feature_store_test = executable(
'src/image_catalog_persistent.c', 'src/image_catalog_persistent.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/feature_store.c', 'src/feature_store.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -286,7 +286,7 @@ feature_file_robustness_test = executable(
'src/image_catalog_persistent.c', 'src/image_catalog_persistent.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/feature_store.c', 'src/feature_store.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -304,7 +304,7 @@ visual_index_test = executable(
'tests/test_visual_index.c', 'src/app_state.c', 'tests/test_visual_index.c', 'src/app_state.c',
'src/image_catalog_persistent.c', 'src/feature_store.c', 'src/image_catalog_persistent.c', 'src/feature_store.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/visual_index.c', 'src/project_db.c', 'src/task.c', 'src/visual_index.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_snapshot.c', 'src/resource_governor.c', 'src/resource_snapshot.c',
], ],
c_args: [ c_args: [
@ -322,7 +322,7 @@ candidate_pair_gen_test = executable(
'src/image_catalog_persistent.c', 'src/feature_store.c', 'src/image_catalog_persistent.c', 'src/feature_store.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/visual_index.c', 'src/candidate_pair_gen.c', 'src/visual_index.c', 'src/candidate_pair_gen.c',
'src/project_db.c', 'src/task.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_snapshot.c', 'src/resource_governor.c', 'src/resource_snapshot.c',
], ],
include_directories: include_directories('include'), include_directories: include_directories('include'),
@ -334,7 +334,7 @@ candidate_pair_task_test = executable(
'test-candidate-pair-task', 'test-candidate-pair-task',
sources: [ sources: [
'tests/test_candidate_pair_task.c', 'src/app_state.c', 'tests/test_candidate_pair_task.c', 'src/app_state.c',
'src/project.c', 'src/project_db.c', 'src/task.c', 'src/project.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', 'src/task.c',
'src/task_checkpoint.c', 'src/task_kind_registry.c', 'src/task_checkpoint.c', 'src/task_kind_registry.c',
'src/task_kinds.c', 'src/matcher_task.c', 'src/matcher.cpp', 'src/task_kinds.c', 'src/matcher_task.c', 'src/matcher.cpp',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp', 'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -364,7 +364,7 @@ matcher_task_test = executable(
'test-matcher-task', 'test-matcher-task',
sources: [ sources: [
'tests/test_matcher_task.c', 'src/app_state.c', 'src/project.c', 'tests/test_matcher_task.c', 'src/app_state.c', 'src/project.c',
'src/project_db.c', 'src/task.c', 'src/task_checkpoint.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', 'src/task.c', 'src/task_checkpoint.c',
'src/task_kind_registry.c', 'src/task_kinds.c', 'src/task_queue.c', 'src/task_kind_registry.c', 'src/task_kinds.c', 'src/task_queue.c',
'src/matcher_task.c', 'src/matcher.cpp', 'src/match_file.c', 'src/matcher_task.c', 'src/matcher.cpp', 'src/match_file.c',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp', 'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -390,7 +390,7 @@ feature_task_test = executable(
'test-feature-task', 'test-feature-task',
sources: [ sources: [
'tests/test_feature_task.c', 'src/app_state.c', 'src/project.c', 'tests/test_feature_task.c', 'src/app_state.c', 'src/project.c',
'src/project_db.c', 'src/task.c', 'src/task_checkpoint.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', 'src/task.c', 'src/task_checkpoint.c',
'src/task_kind_registry.c', 'src/task_kinds.c', 'src/task_queue.c', 'src/task_kind_registry.c', 'src/task_kinds.c', 'src/task_queue.c',
'src/matcher_task.c', 'src/matcher.cpp', 'src/match_file.c', 'src/matcher_task.c', 'src/matcher.cpp', 'src/match_file.c',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp', 'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -424,7 +424,7 @@ precision_consolidation_test = executable(
'test-precision-consolidation', 'test-precision-consolidation',
sources: [ sources: [
'tests/test_precision_consolidation.c', 'src/app_state.c', 'tests/test_precision_consolidation.c', 'src/app_state.c',
'src/project.c', 'src/project_db.c', 'src/task.c', 'src/project.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', 'src/task.c',
'src/task_checkpoint.c', 'src/task_kind_registry.c', 'src/task_checkpoint.c', 'src/task_kind_registry.c',
'src/task_kinds.c', 'src/matcher_task.c', 'src/matcher.cpp', 'src/task_kinds.c', 'src/matcher_task.c', 'src/matcher.cpp',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp', 'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -496,7 +496,7 @@ project_db_test = executable(
'test-project-db', 'test-project-db',
sources: [ sources: [
'tests/test_project_db.c', 'tests/test_project_db.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -508,11 +508,39 @@ project_db_test = executable(
test('project-db', project_db_test, timeout: 30) test('project-db', project_db_test, timeout: 30)
sparse_sfm_model_test = executable(
'test-sparse-sfm-model',
sources: [
'tests/test_sparse_sfm_model.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
],
include_directories: include_directories('include'),
dependencies: [threads, sqlite3, openssl],
)
test('sparse-sfm-model', sparse_sfm_model_test, timeout: 30)
sparse_sfm_resource_test = executable(
'test-sparse-sfm-resource',
sources: [
'tests/test_sparse_sfm_resource.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
],
include_directories: include_directories('include'),
dependencies: [threads, sqlite3, openssl],
)
match_result_test = executable( match_result_test = executable(
'test-match-result', 'test-match-result',
sources: [ sources: [
'tests/test_match_result.c', 'tests/test_match_result.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -528,7 +556,7 @@ geometric_verification_test = executable(
'test-geometric-verification', 'test-geometric-verification',
sources: [ sources: [
'tests/test_geometric_verification.c', 'tests/test_geometric_verification.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -546,7 +574,7 @@ project_test = executable(
'tests/test_project.c', 'tests/test_project.c',
'src/app_state.c', 'src/app_state.c',
'src/project.c', 'src/project.c',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/task_checkpoint.c', 'src/task_checkpoint.c',
'src/task_kind_registry.c', 'src/task_kind_registry.c',
@ -679,7 +707,7 @@ matcher_test = executable(
'src/matcher.cpp', 'src/matcher.cpp',
'src/feature_store.c', 'src/feature_store.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -749,7 +777,7 @@ matcher_e2e_test = executable(
'src/matcher.cpp', 'src/matcher.cpp',
'src/feature_store.c', 'src/feature_store.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -771,7 +799,7 @@ executable(
'src/matcher.cpp', 'src/matcher.cpp',
'src/feature_store.c', 'src/feature_store.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -797,7 +825,7 @@ geometric_verifier_test = executable(
'src/match_file.c', 'src/match_file.c',
'src/feature_store.c', 'src/feature_store.c',
'src/feature_extractor_opencv.cpp', 'src/feature_extractor_opencv.cpp',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/task.c',
'src/resource_governor.c', 'src/resource_governor.c',
'src/resource_snapshot.c', 'src/resource_snapshot.c',
@ -815,7 +843,7 @@ geometric_verifier_task_test = executable(
'test-geometric-verifier-task', 'test-geometric-verifier-task',
sources: [ sources: [
'tests/test_geometric_verifier_task.c', 'src/app_state.c', 'src/project.c', 'tests/test_geometric_verifier_task.c', 'src/app_state.c', 'src/project.c',
'src/project_db.c', 'src/task.c', 'src/task_checkpoint.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c', 'src/task.c', 'src/task_checkpoint.c',
'src/task_kind_registry.c', 'src/task_kinds.c', 'src/task_queue.c', 'src/task_kind_registry.c', 'src/task_kinds.c', 'src/task_queue.c',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp', 'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
'src/match_file.c', 'src/feature_store.c', 'src/resource_governor.c', 'src/match_file.c', 'src/feature_store.c', 'src/resource_governor.c',
@ -851,7 +879,7 @@ track_builder_project_test = executable(
'src/track_builder.cpp', 'src/track_builder.cpp',
'src/track_builder_project.cpp', 'src/track_builder_project.cpp',
'src/track_builder_task.cpp', 'src/track_builder_task.cpp',
'src/project_db.c', 'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/app_state.c', 'src/app_state.c',
'src/task.c', 'src/task.c',
'src/task_kind_registry.c', 'src/task_kind_registry.c',

View file

@ -12,12 +12,7 @@
#include <time.h> #include <time.h>
#include <lardon3d/project_db.h> #include <lardon3d/project_db.h>
#include "project_db_internal.h"
struct Lardon3DProjectDb {
sqlite3 *connection;
pthread_mutex_t mutex;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
};
static const char schema_v5[] = static const char schema_v5[] =
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);" "CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
@ -319,13 +314,122 @@ static const char schema_track_builder_task_v15[] =
"scope_sha256 BLOB NOT NULL CHECK(length(scope_sha256)=32)," "scope_sha256 BLOB NOT NULL CHECK(length(scope_sha256)=32),"
"scope_format_version INTEGER NOT NULL CHECK(scope_format_version=1));"; "scope_format_version INTEGER NOT NULL CHECK(scope_format_version=1));";
static const char schema_sparse_sfm_v16_a[] =
"CREATE TABLE sparse_calibrations("
"calibration_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(calibration_id>0),"
"scientific_hash BLOB NOT NULL CHECK(length(scientific_hash)=32) UNIQUE,"
"model_kind INTEGER NOT NULL CHECK(model_kind=1),model_version INTEGER NOT "
"NULL CHECK(model_version=1),"
"width INTEGER NOT NULL CHECK(width>0),height INTEGER NOT NULL "
"CHECK(height>0),"
"fx REAL NOT NULL,fy REAL NOT NULL,cx REAL NOT NULL,cy REAL NOT NULL,"
"k1 REAL NOT NULL,k2 REAL NOT NULL,p1 REAL NOT NULL,p2 REAL NOT NULL,"
"provenance_kind INTEGER NOT NULL CHECK(provenance_kind IN(1,2)),"
"provenance_fingerprint BLOB NOT NULL "
"CHECK(length(provenance_fingerprint)=32));"
"CREATE TABLE sparse_calibration_scopes("
"scope_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(scope_id>0),"
"scientific_hash BLOB NOT NULL CHECK(length(scientific_hash)=32) UNIQUE,"
"member_count INTEGER NOT NULL CHECK(member_count>0));"
"CREATE TABLE sparse_calibration_scope_images("
"scope_id INTEGER NOT NULL REFERENCES sparse_calibration_scopes(scope_id) "
"ON DELETE CASCADE,"
"image_id INTEGER NOT NULL REFERENCES images(image_id),"
"calibration_id INTEGER NOT NULL REFERENCES "
"sparse_calibrations(calibration_id),"
"PRIMARY KEY(scope_id,image_id));"
"CREATE INDEX sparse_scope_images_calibration_idx ON "
"sparse_calibration_scope_images(scope_id,calibration_id);"
"CREATE INDEX sparse_scope_images_calibration_lookup_idx ON "
"sparse_calibration_scope_images(calibration_id,scope_id);";
static const char schema_sparse_sfm_v16_b[] =
"CREATE TABLE sparse_reconstructions("
"reconstruction_id INTEGER PRIMARY KEY AUTOINCREMENT "
"CHECK(reconstruction_id>0),"
"track_set_id INTEGER NOT NULL REFERENCES track_sets(track_set_id),"
"calibration_scope_id INTEGER NOT NULL REFERENCES "
"sparse_calibration_scopes(scope_id),"
"sfm_kind INTEGER NOT NULL CHECK(sfm_kind=1),sfm_version INTEGER NOT NULL "
"CHECK(sfm_version=1),"
"parameter_fingerprint BLOB NOT NULL "
"CHECK(length(parameter_fingerprint)=32),"
"component_count INTEGER NOT NULL CHECK(component_count>0),"
"registered_image_count INTEGER NOT NULL CHECK(registered_image_count>=2),"
"landmark_count INTEGER NOT NULL CHECK(landmark_count>0),"
"reprojection_rmse_px REAL NOT NULL,reprojection_median_px REAL NOT "
"NULL,created_at INTEGER NOT NULL,"
"UNIQUE(track_set_id,calibration_scope_id,sfm_kind,sfm_version,parameter_"
"fingerprint));"
"CREATE INDEX sparse_reconstructions_scope_idx ON "
"sparse_reconstructions(calibration_scope_id);"
"CREATE TABLE sparse_reconstruction_components("
"component_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(component_id>0),"
"reconstruction_id INTEGER NOT NULL REFERENCES "
"sparse_reconstructions(reconstruction_id) ON DELETE CASCADE,"
"component_key INTEGER NOT NULL "
"CHECK(component_key>0),registered_image_count INTEGER NOT NULL "
"CHECK(registered_image_count>0),"
"landmark_count INTEGER NOT NULL CHECK(landmark_count>0),"
"UNIQUE(reconstruction_id,component_id),UNIQUE(reconstruction_id,component_"
"key));"
"CREATE INDEX sparse_components_page_idx ON "
"sparse_reconstruction_components(reconstruction_id,component_key);"
"CREATE TABLE sparse_registered_images("
"reconstruction_id INTEGER NOT NULL REFERENCES "
"sparse_reconstructions(reconstruction_id) ON DELETE CASCADE,"
"component_id INTEGER NOT NULL, image_id INTEGER NOT NULL REFERENCES "
"images(image_id),"
"rotation_00 REAL NOT NULL,rotation_01 REAL NOT NULL,rotation_02 REAL NOT "
"NULL,"
"rotation_10 REAL NOT NULL,rotation_11 REAL NOT NULL,rotation_12 REAL NOT "
"NULL,"
"rotation_20 REAL NOT NULL,rotation_21 REAL NOT NULL,rotation_22 REAL NOT "
"NULL,"
"translation_0 REAL NOT NULL,translation_1 REAL NOT NULL,translation_2 "
"REAL NOT NULL,"
"PRIMARY KEY(reconstruction_id,image_id),"
"FOREIGN KEY(reconstruction_id,component_id) REFERENCES "
"sparse_reconstruction_components(reconstruction_id,component_id));"
"CREATE INDEX sparse_registered_page_idx ON "
"sparse_registered_images(reconstruction_id,component_id,image_id);"
"CREATE INDEX sparse_registered_image_lookup_idx ON "
"sparse_registered_images(reconstruction_id,image_id);"
"CREATE TABLE sparse_landmarks("
"landmark_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(landmark_id>0),"
"reconstruction_id INTEGER NOT NULL REFERENCES "
"sparse_reconstructions(reconstruction_id) ON DELETE CASCADE,"
"component_id INTEGER NOT NULL,track_id INTEGER NOT NULL REFERENCES "
"tracks(track_id),"
"x REAL NOT NULL,y REAL NOT NULL,z REAL NOT NULL,reprojection_rmse_px REAL "
"NOT NULL,"
"reprojection_median_px REAL NOT NULL,observation_count INTEGER NOT NULL "
"CHECK(observation_count>0),"
"UNIQUE(reconstruction_id,track_id),FOREIGN "
"KEY(reconstruction_id,component_id) "
"REFERENCES "
"sparse_reconstruction_components(reconstruction_id,component_id));"
"CREATE INDEX sparse_landmarks_page_idx ON "
"sparse_landmarks(reconstruction_id,component_id,track_id);"
"CREATE INDEX sparse_landmark_track_lookup_idx ON "
"sparse_landmarks(reconstruction_id,track_id);"
"CREATE TABLE sparse_landmark_observations("
"landmark_id INTEGER NOT NULL REFERENCES sparse_landmarks(landmark_id) ON "
"DELETE CASCADE,"
"feature_set_id INTEGER NOT NULL REFERENCES feature_sets(feature_set_id),"
"feature_index INTEGER NOT NULL CHECK(feature_index>=0),position_in_track "
"INTEGER NOT NULL CHECK(position_in_track>=0),"
"PRIMARY KEY(landmark_id,feature_set_id,feature_index));"
"CREATE INDEX sparse_landmark_observations_page_idx ON "
"sparse_landmark_observations(landmark_id,position_in_track);";
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 : "");
} }
} }
static Lardon3DProjectDbResult sqlite_result(Lardon3DProjectDb *database, int code, Lardon3DProjectDbResult sqlite_result(Lardon3DProjectDb *database, int code,
const char *context) { const char *context) {
(void)snprintf(database->error, sizeof(database->error), "%s: %s", context, (void)snprintf(database->error, sizeof(database->error), "%s: %s", context,
sqlite3_errmsg(database->connection)); sqlite3_errmsg(database->connection));
@ -367,7 +471,7 @@ static bool database_time(sqlite3_int64 value, time_t *output) {
return true; return true;
} }
static Lardon3DProjectDbResult execute(Lardon3DProjectDb *database, const char *sql, Lardon3DProjectDbResult execute(Lardon3DProjectDb *database, const char *sql,
const char *context) { const char *context) {
char *message = NULL; char *message = NULL;
int code = sqlite3_exec(database->connection, sql, NULL, NULL, &message); int code = sqlite3_exec(database->connection, sql, NULL, NULL, &message);
@ -380,7 +484,7 @@ static Lardon3DProjectDbResult execute(Lardon3DProjectDb *database, const char *
return sqlite_result(database, code, context); return sqlite_result(database, code, context);
} }
static Lardon3DProjectDbResult prepare(Lardon3DProjectDb *database, const char *sql, Lardon3DProjectDbResult prepare(Lardon3DProjectDb *database, const char *sql,
sqlite3_stmt **statement) { sqlite3_stmt **statement) {
int code = sqlite3_prepare_v2(database->connection, sql, -1, statement, NULL); int code = sqlite3_prepare_v2(database->connection, sql, -1, statement, NULL);
return code == SQLITE_OK ? LARDON3D_PROJECT_DB_OK : sqlite_result(database, code, "prepare"); return code == SQLITE_OK ? LARDON3D_PROJECT_DB_OK : sqlite_result(database, code, "prepare");
@ -409,7 +513,8 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
if (from_version != 0 && from_version != 1 && from_version != 2 && from_version != 3 && if (from_version != 0 && from_version != 1 && from_version != 2 && from_version != 3 &&
from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 && from_version != 4 && from_version != 5 && from_version != 6 && from_version != 7 &&
from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 && from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 &&
from_version != 12 && from_version != 13 && from_version != 14) { from_version != 12 && from_version != 13 && from_version != 14 &&
from_version != 15) {
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");
@ -713,6 +818,27 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
"finish schema v15 migration"); "finish schema v15 migration");
} }
} }
if (result == LARDON3D_PROJECT_DB_OK && from_version < 16) {
result = execute(database, schema_sparse_sfm_v16_a,
"migrate schema v15 to v16 (part 1)");
#ifdef LARDON3D_PROJECT_DB_TESTING
if (result == LARDON3D_PROJECT_DB_OK &&
getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V16")) {
result = execute(database, "INSERT INTO missing_v16_test_table VALUES(1)",
"forced migration v16 failure");
}
#endif
if (result == LARDON3D_PROJECT_DB_OK) {
result = execute(database, schema_sparse_sfm_v16_b,
"migrate schema v15 to v16 (part 2)");
}
if (result == LARDON3D_PROJECT_DB_OK) {
result = execute(
database,
"UPDATE metadata SET value=16 WHERE key='schema_version' AND value=15",
"finish schema v16 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");
} }
@ -833,7 +959,15 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
"track_sets", "track_sets",
"tracks", "tracks",
"track_observations", "track_observations",
"track_builder_tasks"}; "track_builder_tasks",
"sparse_calibrations",
"sparse_calibration_scopes",
"sparse_calibration_scope_images",
"sparse_reconstructions",
"sparse_reconstruction_components",
"sparse_registered_images",
"sparse_landmarks",
"sparse_landmark_observations"};
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) {
@ -908,7 +1042,7 @@ Lardon3DProjectDbResult lardon3d_project_db_legacy_catalog_pending(Lardon3DProje
return result; return result;
} }
static Lardon3DProjectDbResult step_done(Lardon3DProjectDb *database, sqlite3_stmt *statement, Lardon3DProjectDbResult step_done(Lardon3DProjectDb *database, sqlite3_stmt *statement,
const char *context) { const char *context) {
int code = sqlite3_step(statement); int code = sqlite3_step(statement);
if (code != SQLITE_DONE) { if (code != SQLITE_DONE) {

24
src/project_db_internal.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef LARDON3D_PROJECT_DB_INTERNAL_H
#define LARDON3D_PROJECT_DB_INTERNAL_H
#include <pthread.h>
#include <sqlite3.h>
#include <lardon3d/project_db.h>
struct Lardon3DProjectDb {
sqlite3 *connection;
pthread_mutex_t mutex;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
};
Lardon3DProjectDbResult sqlite_result(Lardon3DProjectDb *database, int code,
const char *context);
Lardon3DProjectDbResult execute(Lardon3DProjectDb *database, const char *sql,
const char *context);
Lardon3DProjectDbResult prepare(Lardon3DProjectDb *database, const char *sql,
sqlite3_stmt **statement);
Lardon3DProjectDbResult step_done(Lardon3DProjectDb *database,
sqlite3_stmt *statement, const char *context);
#endif

1522
src/project_db_sparse_sfm.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -9,12 +9,12 @@
#include <lardon3d/project_db.h> #include <lardon3d/project_db.h>
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \ fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \ return false; \
} \ } \
} while (0) } while (0)
typedef struct { typedef struct {
@ -35,20 +35,24 @@ static bool execute_sql(const char *path, const char *sql) {
return sqlite3_close(connection) == SQLITE_OK && ok; return sqlite3_close(connection) == SQLITE_OK && ok;
} }
static bool query_integer(const char *path, const char *sql, sqlite3_int64 expected) { static bool query_integer(const char *path, const char *sql,
sqlite3_int64 expected) {
sqlite3 *connection = NULL; sqlite3 *connection = NULL;
sqlite3_stmt *statement = NULL; sqlite3_stmt *statement = NULL;
if (sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK || if (sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) !=
SQLITE_OK ||
sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) != SQLITE_OK) { sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) != SQLITE_OK) {
if (statement) (void)sqlite3_finalize(statement); if (statement)
if (connection) (void)sqlite3_close(connection); (void)sqlite3_finalize(statement);
if (connection)
(void)sqlite3_close(connection);
return false; return false;
} }
bool matches = sqlite3_step(statement) == SQLITE_ROW && bool matches = sqlite3_step(statement) == SQLITE_ROW &&
sqlite3_column_int64(statement, 0) == expected && sqlite3_column_int64(statement, 0) == expected &&
sqlite3_step(statement) == SQLITE_DONE; sqlite3_step(statement) == SQLITE_DONE;
return sqlite3_finalize(statement) == SQLITE_OK && sqlite3_close(connection) == SQLITE_OK && return sqlite3_finalize(statement) == SQLITE_OK &&
matches; sqlite3_close(connection) == SQLITE_OK && matches;
} }
static void asset_path(const unsigned char hash[32], const char *kind, static void asset_path(const unsigned char hash[32], const char *kind,
@ -60,13 +64,14 @@ static void asset_path(const unsigned char hash[32], const char *kind,
hex[index * 2 + 1] = digits[hash[index] & 15U]; hex[index * 2 + 1] = digits[hash[index] & 15U];
} }
hex[64] = '\0'; hex[64] = '\0';
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, "assets/%s/%c%c/%s", kind, hex[0], (void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, "assets/%s/%c%c/%s",
hex[1], hex); kind, hex[0], hex[1], hex);
} }
static bool create_match_result(Lardon3DProjectDb *database, uint64_t pair_id, uint64_t fs_a, static bool create_match_result(Lardon3DProjectDb *database, uint64_t pair_id,
uint64_t fs_b, uint32_t version, unsigned char fingerprint_byte, uint64_t fs_a, uint64_t fs_b, uint32_t version,
int status, uint32_t match_count, uint64_t *id) { unsigned char fingerprint_byte, int status,
uint32_t match_count, uint64_t *id) {
unsigned char fingerprint[32] = {0}; unsigned char fingerprint[32] = {0};
unsigned char hash[32] = {0}; unsigned char hash[32] = {0};
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY]; char path[LARDON3D_PROJECT_DB_PATH_CAPACITY];
@ -75,10 +80,12 @@ static bool create_match_result(Lardon3DProjectDb *database, uint64_t pair_id, u
asset_path(hash, "matches", path); asset_path(hash, "matches", path);
Lardon3DProjectDbMatchResult result; Lardon3DProjectDbMatchResult result;
Lardon3DProjectDbResult db_result = lardon3d_project_db_create_match_result( Lardon3DProjectDbResult db_result = lardon3d_project_db_create_match_result(
database, pair_id, fs_a, fs_b, "model-test", version, fingerprint, status, match_count, database, pair_id, fs_a, fs_b, "model-test", version, fingerprint, status,
status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? hash : NULL, match_count, status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? hash : NULL,
status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? path : NULL, status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? path : NULL,
status == LARDON3D_MATCH_RESULT_STATUS_MATCHED ? 32U + (uint64_t)match_count * 12U : 0, status == LARDON3D_MATCH_RESULT_STATUS_MATCHED
? 32U + (uint64_t)match_count * 12U
: 0,
version, &result); version, &result);
if (db_result != LARDON3D_PROJECT_DB_OK) { if (db_result != LARDON3D_PROJECT_DB_OK) {
return false; return false;
@ -89,8 +96,8 @@ static bool create_match_result(Lardon3DProjectDb *database, uint64_t pair_id, u
static bool create_parents(Lardon3DProjectDb *database, Parents *parents) { static bool create_parents(Lardon3DProjectDb *database, Parents *parents) {
Lardon3DProjectDbScanSet scanset; Lardon3DProjectDbScanSet scanset;
if (lardon3d_project_db_create_scanset(database, "Geometry-model", &scanset) != if (lardon3d_project_db_create_scanset(database, "Geometry-model",
LARDON3D_PROJECT_DB_OK) { &scanset) != LARDON3D_PROJECT_DB_OK) {
return false; return false;
} }
unsigned char image_hash_a[32] = {0x11}; unsigned char image_hash_a[32] = {0x11};
@ -102,17 +109,20 @@ static bool create_parents(Lardon3DProjectDb *database, Parents *parents) {
Lardon3DProjectDbImageRegisterStatus register_status; Lardon3DProjectDbImageRegisterStatus register_status;
Lardon3DProjectDbImage image_a; Lardon3DProjectDbImage image_a;
Lardon3DProjectDbImage image_b; Lardon3DProjectDbImage image_b;
if (lardon3d_project_db_register_image(database, scanset.scanset_id, image_hash_a, image_path_a, if (lardon3d_project_db_register_image(
10, "a.jpg", "/a.jpg", 0, 1, &register_status, database, scanset.scanset_id, image_hash_a, image_path_a, 10, "a.jpg",
&image_a) != LARDON3D_PROJECT_DB_OK || "/a.jpg", 0, 1, &register_status,
lardon3d_project_db_register_image(database, scanset.scanset_id, image_hash_b, image_path_b, &image_a) != LARDON3D_PROJECT_DB_OK ||
10, "b.jpg", "/b.jpg", 0, 2, &register_status, lardon3d_project_db_register_image(
&image_b) != LARDON3D_PROJECT_DB_OK) { database, scanset.scanset_id, image_hash_b, image_path_b, 10, "b.jpg",
"/b.jpg", 0, 2, &register_status,
&image_b) != LARDON3D_PROJECT_DB_OK) {
return false; return false;
} }
Lardon3DProjectDbCandidatePair pair; Lardon3DProjectDbCandidatePair pair;
if (lardon3d_project_db_create_candidate_pair(database, image_a.image_id, image_b.image_id, 3, if (lardon3d_project_db_create_candidate_pair(database, image_a.image_id,
&pair) != LARDON3D_PROJECT_DB_OK) { image_b.image_id, 3, &pair) !=
LARDON3D_PROJECT_DB_OK) {
return false; return false;
} }
unsigned char feature_hash_a[32] = {0x33}; unsigned char feature_hash_a[32] = {0x33};
@ -124,53 +134,67 @@ static bool create_parents(Lardon3DProjectDb *database, Parents *parents) {
Lardon3DProjectDbFeatureSet feature_a; Lardon3DProjectDbFeatureSet feature_a;
Lardon3DProjectDbFeatureSet feature_b; Lardon3DProjectDbFeatureSet feature_b;
if (lardon3d_project_db_register_feature_set( if (lardon3d_project_db_register_feature_set(
database, image_a.image_id, "orb", 1, feature_hash_a, image_hash_a, 8192, 1, 32, database, image_a.image_id, "orb", 1, feature_hash_a, image_hash_a,
feature_hash_a, feature_path_a, 100, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 4, 8192, 1, 32, feature_hash_a, feature_path_a, 100,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 4,
&feature_a) != LARDON3D_PROJECT_DB_OK || &feature_a) != LARDON3D_PROJECT_DB_OK ||
lardon3d_project_db_register_feature_set( lardon3d_project_db_register_feature_set(
database, image_b.image_id, "orb", 1, feature_hash_b, image_hash_b, 8192, 1, 32, database, image_b.image_id, "orb", 1, feature_hash_b, image_hash_b,
feature_hash_b, feature_path_b, 100, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 5, 8192, 1, 32, feature_hash_b, feature_path_b, 100,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 5,
&feature_b) != LARDON3D_PROJECT_DB_OK) { &feature_b) != LARDON3D_PROJECT_DB_OK) {
return false; return false;
} }
parents->candidate_pair_id = pair.candidate_pair_id; parents->candidate_pair_id = pair.candidate_pair_id;
parents->feature_set_id_a = feature_a.feature_set_id; parents->feature_set_id_a = feature_a.feature_set_id;
parents->feature_set_id_b = feature_b.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, return create_match_result(database, pair.candidate_pair_id,
feature_b.feature_set_id, 1, 0x51, feature_a.feature_set_id, feature_b.feature_set_id,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 9, &parents->matched_id) && 1, 0x51, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 9,
create_match_result(database, pair.candidate_pair_id, feature_a.feature_set_id, &parents->matched_id) &&
feature_b.feature_set_id, 2, 0x52, create_match_result(database, pair.candidate_pair_id,
LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, &parents->no_match_id) && feature_a.feature_set_id, feature_b.feature_set_id,
create_match_result(database, pair.candidate_pair_id, feature_a.feature_set_id, 2, 0x52, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0,
feature_b.feature_set_id, 3, 0x53, &parents->no_match_id) &&
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 8192, create_match_result(database, pair.candidate_pair_id,
&parents->second_matched_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) { static bool create_v11_database(const char *path) {
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
if (lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK) { if (lardon3d_project_db_open(path, &database, error) !=
LARDON3D_PROJECT_DB_OK) {
return false; return false;
} }
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
return execute_sql(path, return execute_sql(path,
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE geometric_verifier_tasks;" "DROP TABLE IF EXISTS sparse_landmark_observations;"
"DROP TABLE geometric_verification_results;" "DROP TABLE IF EXISTS sparse_landmarks;"
"DROP TABLE track_observations;" "DROP TABLE IF EXISTS sparse_registered_images;"
"DROP TABLE tracks;" "DROP TABLE IF EXISTS sparse_reconstruction_components;"
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;" "DROP TABLE IF EXISTS sparse_reconstructions;"
"UPDATE metadata SET value=11 WHERE key='schema_version';" "DROP TABLE IF EXISTS sparse_calibration_scope_images;"
"DROP TABLE IF EXISTS sparse_calibration_scopes;"
"DROP TABLE IF EXISTS sparse_calibrations;"
"DROP TABLE geometric_verifier_tasks;"
"DROP TABLE geometric_verification_results;"
"DROP TABLE track_observations;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;"
"UPDATE metadata SET value=11 WHERE key='schema_version';"
"COMMIT;PRAGMA foreign_keys=ON;"); "COMMIT;PRAGMA foreign_keys=ON;");
} }
static bool test_model_api(const char *path) { static bool test_model_api(const char *path) {
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(path, &database, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_open(path, &database, error) ==
CHECK(lardon3d_project_db_schema_version(database) == 15); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 16);
Parents parents; Parents parents;
CHECK(create_parents(database, &parents)); CHECK(create_parents(database, &parents));
@ -182,8 +206,9 @@ static bool test_model_api(const char *path) {
double model[9] = {0.0, -1.0, 1e-300, 1e300, 4.0, 5.0, 6.0, 7.0, 8.0}; double model[9] = {0.0, -1.0, 1e-300, 1e300, 4.0, 5.0, 6.0, 7.0, 8.0};
Lardon3DProjectDbGeometricVerificationResult verified; Lardon3DProjectDbGeometricVerificationResult verified;
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), model, 10, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, fingerprint_a,
LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), model, 10,
&verified) == LARDON3D_PROJECT_DB_OK); &verified) == LARDON3D_PROJECT_DB_OK);
CHECK(verified.has_model && verified.inlier_mask_size == 2 && CHECK(verified.has_model && verified.inlier_mask_size == 2 &&
memcmp(verified.model, model, sizeof(model)) == 0); memcmp(verified.model, model, sizeof(model)) == 0);
@ -191,17 +216,20 @@ static bool test_model_api(const char *path) {
unsigned char rejected_mask[2] = {0x03, 0x00}; unsigned char rejected_mask[2] = {0x03, 0x00};
Lardon3DProjectDbGeometricVerificationResult rejected; Lardon3DProjectDbGeometricVerificationResult rejected;
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 2, database, parents.matched_id,
fingerprint_b, LARDON3D_GEOMETRIC_REJECTED, 2, rejected_mask, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 2, fingerprint_b,
sizeof(rejected_mask), NULL, 11, &rejected) == LARDON3D_PROJECT_DB_OK); LARDON3D_GEOMETRIC_REJECTED, 2, rejected_mask,
sizeof(rejected_mask), NULL, 11,
&rejected) == LARDON3D_PROJECT_DB_OK);
CHECK(!rejected.has_model && rejected.inlier_count == 2); CHECK(!rejected.has_model && rejected.inlier_count == 2);
unsigned char zero_mask[2] = {0}; unsigned char zero_mask[2] = {0};
Lardon3DProjectDbGeometricVerificationResult zero_inliers; Lardon3DProjectDbGeometricVerificationResult zero_inliers;
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, database, parents.matched_id,
fingerprint_b, LARDON3D_GEOMETRIC_REJECTED, 0, zero_mask, sizeof(zero_mask), NULL, 11, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, fingerprint_b,
&zero_inliers) == LARDON3D_PROJECT_DB_OK); LARDON3D_GEOMETRIC_REJECTED, 0, zero_mask, sizeof(zero_mask), NULL,
11, &zero_inliers) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbGeometricVerificationResult loaded; Lardon3DProjectDbGeometricVerificationResult loaded;
CHECK(lardon3d_project_db_load_geometric_verification_result( CHECK(lardon3d_project_db_load_geometric_verification_result(
@ -211,17 +239,22 @@ static bool test_model_api(const char *path) {
memcmp(loaded.model, model, sizeof(model)) == 0 && memcmp(loaded.model, model, sizeof(model)) == 0 &&
memcmp(loaded.inlier_mask, mask_a, sizeof(mask_a)) == 0); memcmp(loaded.inlier_mask, mask_a, sizeof(mask_a)) == 0);
CHECK(lardon3d_project_db_find_geometric_verification_result( CHECK(lardon3d_project_db_find_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, database, parents.matched_id,
fingerprint_a, &loaded) == LARDON3D_PROJECT_DB_OK && LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, fingerprint_a,
loaded.geometric_verification_result_id == verified.geometric_verification_result_id); &loaded) == LARDON3D_PROJECT_DB_OK &&
loaded.geometric_verification_result_id ==
verified.geometric_verification_result_id);
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 2, rejected_mask, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, fingerprint_a,
sizeof(rejected_mask), NULL, 12, &loaded) == LARDON3D_PROJECT_DB_CONSTRAINT); LARDON3D_GEOMETRIC_REJECTED, 2, rejected_mask,
sizeof(rejected_mask), NULL, 12,
&loaded) == LARDON3D_PROJECT_DB_CONSTRAINT);
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.no_match_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, database, parents.no_match_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 0, mask_a, 1, NULL, 12, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, fingerprint_a,
LARDON3D_GEOMETRIC_REJECTED, 0, mask_a, 1, NULL, 12,
&loaded) == LARDON3D_PROJECT_DB_CONSTRAINT); &loaded) == LARDON3D_PROJECT_DB_CONSTRAINT);
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, UINT64_C(9223372036854775807), database, UINT64_C(9223372036854775807),
@ -231,60 +264,71 @@ static bool test_model_api(const char *path) {
unsigned char bad_padding[2] = {0x01, 0x80}; unsigned char bad_padding[2] = {0x01, 0x80};
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 1, bad_padding, sizeof(bad_padding), LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, fingerprint_a,
LARDON3D_GEOMETRIC_REJECTED, 1, bad_padding, sizeof(bad_padding),
NULL, 12, &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); NULL, 12, &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 2, mask_a, sizeof(mask_a), NULL, 12, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, fingerprint_a,
LARDON3D_GEOMETRIC_REJECTED, 2, mask_a, sizeof(mask_a), NULL, 12,
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
unsigned char all_inliers[2] = {0xff, 0x01}; unsigned char all_inliers[2] = {0xff, 0x01};
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 4, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 9, all_inliers, sizeof(all_inliers), NULL, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 4, fingerprint_a,
12, &loaded) == LARDON3D_PROJECT_DB_OK); LARDON3D_GEOMETRIC_REJECTED, 9, all_inliers, sizeof(all_inliers),
NULL, 12, &loaded) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 5, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 10, all_inliers, sizeof(all_inliers), NULL, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 5, fingerprint_a,
12, &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 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( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), NULL, 12, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, fingerprint_a,
LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), NULL, 12,
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 1, mask_a, sizeof(mask_a), model, 12, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, fingerprint_a,
LARDON3D_GEOMETRIC_REJECTED, 1, mask_a, sizeof(mask_a), model, 12,
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
double bad_model[9] = {0}; double bad_model[9] = {0};
bad_model[4] = NAN; bad_model[4] = NAN;
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), bad_model, 12, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, fingerprint_a,
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), bad_model,
12, &loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
bad_model[4] = INFINITY; bad_model[4] = INFINITY;
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, database, parents.matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_VERIFIED, 1, mask_a, sizeof(mask_a), bad_model, 12, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 3, fingerprint_a,
&loaded) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 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}; unsigned char max_mask[LARDON3D_PROJECT_DB_INLIER_MASK_MAX] = {0};
max_mask[0] = 0x01; max_mask[0] = 0x01;
max_mask[1023] = 0x80; max_mask[1023] = 0x80;
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.second_matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, database, parents.second_matched_id,
fingerprint_a, LARDON3D_GEOMETRIC_REJECTED, 2, max_mask, sizeof(max_mask), NULL, 13, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, fingerprint_a,
&loaded) == LARDON3D_PROJECT_DB_OK); 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); 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}; static const uint32_t boundary_counts[] = {1, 2, 7, 8, 9, 63, 64, 65, 8191};
for (size_t boundary_index = 0; for (size_t boundary_index = 0;
boundary_index < sizeof(boundary_counts) / sizeof(boundary_counts[0]); ++boundary_index) { boundary_index < sizeof(boundary_counts) / sizeof(boundary_counts[0]);
++boundary_index) {
uint32_t match_count = boundary_counts[boundary_index]; uint32_t match_count = boundary_counts[boundary_index];
uint64_t parent_id = 0; uint64_t parent_id = 0;
CHECK(create_match_result(database, parents.candidate_pair_id, parents.feature_set_id_a, CHECK(create_match_result(
parents.feature_set_id_b, (uint32_t)(10U + boundary_index), database, parents.candidate_pair_id, parents.feature_set_id_a,
(unsigned char)(0x60U + boundary_index), parents.feature_set_id_b, (uint32_t)(10U + boundary_index),
LARDON3D_MATCH_RESULT_STATUS_MATCHED, match_count, &parent_id)); (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}; unsigned char boundary_mask[LARDON3D_PROJECT_DB_INLIER_MASK_MAX] = {0};
size_t boundary_size = ((size_t)match_count + 7U) / 8U; size_t boundary_size = ((size_t)match_count + 7U) / 8U;
uint32_t last_index = match_count - 1U; uint32_t last_index = match_count - 1U;
@ -294,33 +338,38 @@ static bool test_model_api(const char *path) {
sizeof(boundary_fingerprint)); sizeof(boundary_fingerprint));
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parent_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, database, parent_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1,
boundary_fingerprint, LARDON3D_GEOMETRIC_REJECTED, 1, boundary_mask, boundary_fingerprint, LARDON3D_GEOMETRIC_REJECTED, 1,
boundary_size, NULL, 20 + (int64_t)boundary_index, &loaded) == boundary_mask, boundary_size, NULL, 20 + (int64_t)boundary_index,
LARDON3D_PROJECT_DB_OK); &loaded) == LARDON3D_PROJECT_DB_OK);
CHECK(loaded.inlier_mask_size == boundary_size && CHECK(loaded.inlier_mask_size == boundary_size &&
loaded.inlier_mask[last_index / 8U] == (unsigned char)(1U << (last_index % 8U))); loaded.inlier_mask[last_index / 8U] ==
(unsigned char)(1U << (last_index % 8U)));
} }
Lardon3DProjectDbGeometricVerificationResult page[2]; Lardon3DProjectDbGeometricVerificationResult page[2];
size_t count = 0; size_t count = 0;
CHECK(lardon3d_project_db_list_geometric_verification_results( CHECK(lardon3d_project_db_list_geometric_verification_results(
database, parents.matched_id, 0, page, 1, &count) == LARDON3D_PROJECT_DB_OK && database, parents.matched_id, 0, page, 1, &count) ==
LARDON3D_PROJECT_DB_OK &&
count == 1); count == 1);
uint64_t cursor = page[0].geometric_verification_result_id; uint64_t cursor = page[0].geometric_verification_result_id;
CHECK(lardon3d_project_db_list_geometric_verification_results( CHECK(lardon3d_project_db_list_geometric_verification_results(
database, parents.matched_id, cursor, page, 2, &count) == LARDON3D_PROJECT_DB_OK && database, parents.matched_id, cursor, page, 2, &count) ==
LARDON3D_PROJECT_DB_OK &&
count == 2 && page[0].geometric_verification_result_id > cursor); count == 2 && page[0].geometric_verification_result_id > cursor);
CHECK(lardon3d_project_db_list_geometric_verification_results( CHECK(lardon3d_project_db_list_geometric_verification_results(
database, parents.matched_id, 0, page, database, parents.matched_id, 0, page,
LARDON3D_PROJECT_DB_GEOMETRIC_RESULT_PAGE_MAX + 1U, &count) == LARDON3D_PROJECT_DB_GEOMETRIC_RESULT_PAGE_MAX + 1U,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &count) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
uint64_t verified_id = verified.geometric_verification_result_id; uint64_t verified_id = verified.geometric_verification_result_id;
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_open(path, &database, error) ==
CHECK(lardon3d_project_db_load_geometric_verification_result(database, verified_id, &loaded) == LARDON3D_PROJECT_DB_OK);
LARDON3D_PROJECT_DB_OK && memcmp(loaded.model, model, sizeof(model)) == 0); 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); lardon3d_project_db_close(database);
return true; return true;
} }
@ -329,12 +378,13 @@ static bool loader_rejects_after_sql(const char *path, const char *sql) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
if (!execute_sql(path, sql) || if (!execute_sql(path, sql) ||
lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK) { lardon3d_project_db_open(path, &database, error) !=
LARDON3D_PROJECT_DB_OK) {
return false; return false;
} }
Lardon3DProjectDbGeometricVerificationResult result; Lardon3DProjectDbGeometricVerificationResult result;
bool rejected = lardon3d_project_db_load_geometric_verification_result(database, 1, &result) == bool rejected = lardon3d_project_db_load_geometric_verification_result(
LARDON3D_PROJECT_DB_CORRUPT; database, 1, &result) == LARDON3D_PROJECT_DB_CORRUPT;
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
return rejected; return rejected;
} }
@ -342,37 +392,55 @@ static bool loader_rejects_after_sql(const char *path, const char *sql) {
static bool test_corruption(const char *path) { static bool test_corruption(const char *path) {
static const char reset[] = static const char reset[] =
"PRAGMA ignore_check_constraints=ON;" "PRAGMA ignore_check_constraints=ON;"
"UPDATE geometric_verification_results SET verifier_kind=1,verifier_version=1," "UPDATE geometric_verification_results SET "
"parameter_fingerprint=zeroblob(32),status=2,inlier_count=1,inlier_mask=x'0100'," "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_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;"; "model_m20=0,model_m21=0,model_m22=0 WHERE "
"geometric_verification_result_id=1;";
CHECK(execute_sql(path, reset)); CHECK(execute_sql(path, reset));
static const char *corruptions[] = { static const char *corruptions[] = {
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "PRAGMA ignore_check_constraints=ON;UPDATE "
"geometric_verification_results "
"SET verifier_kind=9 WHERE geometric_verification_result_id=1;", "SET verifier_kind=9 WHERE geometric_verification_result_id=1;",
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "PRAGMA ignore_check_constraints=ON;UPDATE "
"SET verifier_kind=1,verifier_version=0 WHERE geometric_verification_result_id=1;", "geometric_verification_results "
"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' " "SET verifier_version=1,parameter_fingerprint=x'01' "
"WHERE geometric_verification_result_id=1;", "WHERE geometric_verification_result_id=1;",
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "PRAGMA ignore_check_constraints=ON;UPDATE "
"geometric_verification_results "
"SET parameter_fingerprint=zeroblob(32),status=9 " "SET parameter_fingerprint=zeroblob(32),status=9 "
"WHERE geometric_verification_result_id=1;", "WHERE geometric_verification_result_id=1;",
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "PRAGMA ignore_check_constraints=ON;UPDATE "
"SET status=2,inlier_count=9000 WHERE geometric_verification_result_id=1;", "geometric_verification_results "
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "SET status=2,inlier_count=9000 WHERE "
"SET inlier_count=1,inlier_mask=x'03' WHERE geometric_verification_result_id=1;", "geometric_verification_result_id=1;",
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "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;", "SET inlier_mask=x'0180' WHERE geometric_verification_result_id=1;",
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "PRAGMA ignore_check_constraints=ON;UPDATE "
"geometric_verification_results "
"SET inlier_mask=x'0300' WHERE geometric_verification_result_id=1;", "SET inlier_mask=x'0300' WHERE geometric_verification_result_id=1;",
"PRAGMA ignore_check_constraints=ON;UPDATE geometric_verification_results " "PRAGMA ignore_check_constraints=ON;UPDATE "
"SET inlier_mask=x'0100',model_m00=NULL WHERE geometric_verification_result_id=1;", "geometric_verification_results "
"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;", "SET model_m00=1e999 WHERE geometric_verification_result_id=1;",
}; };
for (size_t index = 0; index < sizeof(corruptions) / sizeof(corruptions[0]); ++index) { for (size_t index = 0; index < sizeof(corruptions) / sizeof(corruptions[0]);
++index) {
CHECK(execute_sql(path, reset)); CHECK(execute_sql(path, reset));
CHECK(loader_rejects_after_sql(path, corruptions[index])); CHECK(loader_rejects_after_sql(path, corruptions[index]));
} }
@ -380,15 +448,17 @@ static bool test_corruption(const char *path) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
CHECK(execute_sql(path, reset)); CHECK(execute_sql(path, reset));
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_open(path, &database, error) ==
Lardon3DProjectDbGeometricVerificationResult result;
CHECK(lardon3d_project_db_load_geometric_verification_result(database, 1, &result) ==
LARDON3D_PROJECT_DB_OK); 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; uint64_t parent_id = result.match_result_id;
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
char parent_corruption[512]; char parent_corruption[512];
CHECK(snprintf(parent_corruption, sizeof(parent_corruption), CHECK(snprintf(parent_corruption, sizeof(parent_corruption),
"PRAGMA ignore_check_constraints=ON;UPDATE match_results SET result_status=0 " "PRAGMA ignore_check_constraints=ON;UPDATE match_results SET "
"result_status=0 "
"WHERE match_result_id=%llu;", "WHERE match_result_id=%llu;",
(unsigned long long)parent_id) > 0); (unsigned long long)parent_id) > 0);
CHECK(loader_rejects_after_sql(path, parent_corruption)); CHECK(loader_rejects_after_sql(path, parent_corruption));
@ -399,46 +469,60 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
CHECK(create_v11_database(v11_path)); CHECK(create_v11_database(v11_path));
CHECK(query_integer(v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 11)); CHECK(query_integer(
CHECK(query_integer(v11_path, v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 11));
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " CHECK(
"name='geometric_verification_results'", query_integer(v11_path,
0)); "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
CHECK(lardon3d_project_db_open(v11_path, &database, error) == LARDON3D_PROJECT_DB_OK); "name='geometric_verification_results'",
CHECK(lardon3d_project_db_schema_version(database) == 15); 0));
CHECK(lardon3d_project_db_open(v11_path, &database, error) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 16);
Parents parents; Parents parents;
CHECK(create_parents(database, &parents)); CHECK(create_parents(database, &parents));
unsigned char fingerprint[32] = {0x91}; unsigned char fingerprint[32] = {0x91};
unsigned char mask[2] = {0x01, 0x00}; unsigned char mask[2] = {0x01, 0x00};
Lardon3DProjectDbGeometricVerificationResult migrated_result; Lardon3DProjectDbGeometricVerificationResult migrated_result;
CHECK(lardon3d_project_db_create_geometric_verification_result( CHECK(lardon3d_project_db_create_geometric_verification_result(
database, parents.matched_id, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, database, parents.matched_id,
fingerprint, LARDON3D_GEOMETRIC_REJECTED, 1, mask, sizeof(mask), NULL, 30, LARDON3D_GEOMETRIC_VERIFIER_FUNDAMENTAL, 1, fingerprint,
LARDON3D_GEOMETRIC_REJECTED, 1, mask, sizeof(mask), NULL, 30,
&migrated_result) == LARDON3D_PROJECT_DB_OK); &migrated_result) == LARDON3D_PROJECT_DB_OK);
uint64_t migrated_result_id = migrated_result.geometric_verification_result_id; uint64_t migrated_result_id =
migrated_result.geometric_verification_result_id;
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
CHECK(query_integer(v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 15)); CHECK(query_integer(
CHECK(query_integer(v11_path, v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
"SELECT count(*) FROM sqlite_master WHERE type='index' AND " CHECK(
"name='geometric_verification_results_parent_idx'", query_integer(v11_path,
1)); "SELECT count(*) FROM sqlite_master WHERE type='index' AND "
CHECK(lardon3d_project_db_open(v11_path, &database, error) == LARDON3D_PROJECT_DB_OK); "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( CHECK(lardon3d_project_db_load_geometric_verification_result(
database, migrated_result_id, &migrated_result) == LARDON3D_PROJECT_DB_OK && database, migrated_result_id, &migrated_result) ==
LARDON3D_PROJECT_DB_OK &&
migrated_result.match_result_id == parents.matched_id); migrated_result.match_result_id == parents.matched_id);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
CHECK(create_v11_database(failed_path)); CHECK(create_v11_database(failed_path));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V12", "1", 1) == 0); 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(lardon3d_project_db_open(failed_path, &database, error) ==
LARDON3D_PROJECT_DB_IO_ERROR);
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V12") == 0); 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, CHECK(query_integer(failed_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " "SELECT value FROM metadata WHERE key='schema_version'",
"name='geometric_verification_results'", 11));
0)); CHECK(
CHECK(lardon3d_project_db_open(failed_path, &database, error) == LARDON3D_PROJECT_DB_OK); query_integer(failed_path,
CHECK(lardon3d_project_db_schema_version(database) == 15); "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) == 16);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
return true; return true;
} }
@ -449,9 +533,11 @@ static bool run_test(void) {
char database_path[512]; char database_path[512];
char v11_path[512]; char v11_path[512];
char failed_path[512]; char failed_path[512];
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db", directory) > 0); 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(v11_path, sizeof(v11_path), "%s/v11.db", directory) > 0);
CHECK(snprintf(failed_path, sizeof(failed_path), "%s/failed-v12.db", directory) > 0); CHECK(snprintf(failed_path, sizeof(failed_path), "%s/failed-v12.db",
directory) > 0);
CHECK(test_model_api(database_path)); CHECK(test_model_api(database_path));
CHECK(test_corruption(database_path)); CHECK(test_corruption(database_path));
CHECK(test_migration(v11_path, failed_path)); CHECK(test_migration(v11_path, failed_path));
@ -462,6 +548,4 @@ static bool run_test(void) {
return true; return true;
} }
int main(void) { int main(void) { return run_test() ? EXIT_SUCCESS : EXIT_FAILURE; }
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
}

View file

@ -331,13 +331,21 @@ static bool run_task_test(void) {
lardon3d_project_db_close(state.project_db); lardon3d_project_db_close(state.project_db);
CHECK(exec_sql( CHECK(exec_sql(
database_path, database_path,
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE geometric_verifier_tasks;" "DROP TABLE IF EXISTS sparse_landmark_observations;"
"DROP TABLE track_observations;" "DROP TABLE IF EXISTS sparse_landmarks;"
"DROP TABLE tracks;" "DROP TABLE IF EXISTS sparse_registered_images;"
"DROP TABLE track_sets;" "DROP TABLE IF EXISTS sparse_reconstruction_components;"
"DROP TABLE track_builder_tasks;" "DROP TABLE IF EXISTS sparse_reconstructions;"
"UPDATE metadata SET value=12 WHERE key='schema_version';COMMIT;")); "DROP TABLE IF EXISTS sparse_calibration_scope_images;"
"DROP TABLE IF EXISTS sparse_calibration_scopes;"
"DROP TABLE IF EXISTS sparse_calibrations;"
"DROP TABLE geometric_verifier_tasks;"
"DROP TABLE track_observations;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;"
"DROP TABLE track_builder_tasks;"
"UPDATE metadata SET value=12 WHERE key='schema_version';COMMIT;"));
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V13", "1", 1) == 0); CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V13", "1", 1) == 0);
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
CHECK(lardon3d_project_db_open(database_path, &database, error) == CHECK(lardon3d_project_db_open(database_path, &database, error) ==
@ -353,7 +361,7 @@ static bool run_task_test(void) {
0)); 0));
CHECK(lardon3d_project_db_open(database_path, &database, error) == CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 15); CHECK(lardon3d_project_db_schema_version(database) == 16);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
CHECK(remove_tree(root)); CHECK(remove_tree(root));
return true; return true;

View file

@ -11,18 +11,20 @@
#include <lardon3d/project_db.h> #include <lardon3d/project_db.h>
#define CHECK(condition) \ #define CHECK(condition) \
do { \ do { \
if (!(condition)) { \ if (!(condition)) { \
fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \ fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \ return false; \
} \ } \
} while (0) } while (0)
static bool query_integer(const char *path, const char *sql, sqlite3_int64 expected) { static bool query_integer(const char *path, const char *sql,
sqlite3_int64 expected) {
sqlite3 *connection = NULL; sqlite3 *connection = NULL;
sqlite3_stmt *statement = NULL; sqlite3_stmt *statement = NULL;
if (sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) != SQLITE_OK || if (sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) !=
SQLITE_OK ||
sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) != SQLITE_OK) { sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) != SQLITE_OK) {
if (statement) { if (statement) {
(void)sqlite3_finalize(statement); (void)sqlite3_finalize(statement);
@ -35,33 +37,46 @@ static bool query_integer(const char *path, const char *sql, sqlite3_int64 expec
bool matches = sqlite3_step(statement) == SQLITE_ROW && bool matches = sqlite3_step(statement) == SQLITE_ROW &&
sqlite3_column_int64(statement, 0) == expected && sqlite3_column_int64(statement, 0) == expected &&
sqlite3_step(statement) == SQLITE_DONE; sqlite3_step(statement) == SQLITE_DONE;
return sqlite3_finalize(statement) == SQLITE_OK && sqlite3_close(connection) == SQLITE_OK && return sqlite3_finalize(statement) == SQLITE_OK &&
matches; sqlite3_close(connection) == SQLITE_OK && matches;
} }
static bool create_v9_database(const char *path) { static bool create_v9_database(const char *path) {
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
if (lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK) return false; if (lardon3d_project_db_open(path, &database, error) !=
LARDON3D_PROJECT_DB_OK)
return false;
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
sqlite3 *connection = NULL; sqlite3 *connection = NULL;
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 IF EXISTS sparse_landmark_observations;"
"DROP TABLE IF EXISTS sparse_landmarks;"
"DROP TABLE IF EXISTS sparse_registered_images;"
"DROP TABLE IF EXISTS sparse_reconstruction_components;"
"DROP TABLE IF EXISTS sparse_reconstructions;"
"DROP TABLE IF EXISTS sparse_calibration_scope_images;"
"DROP TABLE IF EXISTS sparse_calibration_scopes;"
"DROP TABLE IF EXISTS sparse_calibrations;"
"DROP TABLE geometric_verifier_tasks;" "DROP TABLE geometric_verifier_tasks;"
"DROP TABLE geometric_verification_results;" "DROP TABLE geometric_verification_results;"
"DROP TABLE matcher_tasks;" "DROP TABLE matcher_tasks;"
"DROP TABLE match_results;" "DROP TABLE match_results;"
"DROP TABLE track_observations;" "DROP TABLE track_observations;"
"DROP TABLE tracks;" "DROP TABLE tracks;"
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;" "DROP TABLE track_sets;DROP TABLE track_builder_tasks;"
"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;";
bool ok = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK; bool ok = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
return sqlite3_close(connection) == SQLITE_OK && ok; return sqlite3_close(connection) == SQLITE_OK && ok;
} }
static void asset_path_for_hash(const unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE], static void
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY]) { asset_path_for_hash(const unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE],
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY]) {
static const char digits[] = "0123456789abcdef"; static const char digits[] = "0123456789abcdef";
char hex[65]; char hex[65];
for (size_t i = 0; i < LARDON3D_PROJECT_DB_SHA256_SIZE; ++i) { for (size_t i = 0; i < LARDON3D_PROJECT_DB_SHA256_SIZE; ++i) {
@ -69,8 +84,8 @@ static void asset_path_for_hash(const unsigned char hash[LARDON3D_PROJECT_DB_SHA
hex[i * 2 + 1] = digits[hash[i] & 15U]; hex[i * 2 + 1] = digits[hash[i] & 15U];
} }
hex[64] = '\0'; hex[64] = '\0';
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, "assets/images/%c%c/%s", hex[0], hex[1], (void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY,
hex); "assets/images/%c%c/%s", hex[0], hex[1], hex);
} }
static void feature_asset_path_for_hash( static void feature_asset_path_for_hash(
@ -83,22 +98,25 @@ static void feature_asset_path_for_hash(
hex[i * 2 + 1] = digits[hash[i] & 15U]; hex[i * 2 + 1] = digits[hash[i] & 15U];
} }
hex[64] = '\0'; hex[64] = '\0';
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, "assets/features/%c%c/%s", hex[0], hex[1], (void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY,
hex); "assets/features/%c%c/%s", hex[0], hex[1], hex);
} }
static bool run_test(void) { static bool run_test(void) {
char directory[] = "/tmp/lardon3d-match-result-XXXXXX"; char directory[] = "/tmp/lardon3d-match-result-XXXXXX";
CHECK(mkdtemp(directory)); CHECK(mkdtemp(directory));
char database_path[512], v9_path[512], failed_v10_path[512]; char database_path[512], v9_path[512], failed_v10_path[512];
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db", directory) > 0); CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db",
directory) > 0);
CHECK(snprintf(v9_path, sizeof(v9_path), "%s/v9.db", directory) > 0); CHECK(snprintf(v9_path, sizeof(v9_path), "%s/v9.db", directory) > 0);
CHECK(snprintf(failed_v10_path, sizeof(failed_v10_path), "%s/failed-v10.db", directory) > 0); CHECK(snprintf(failed_v10_path, sizeof(failed_v10_path), "%s/failed-v10.db",
directory) > 0);
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL; Lardon3DProjectDb *database = NULL;
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_open(database_path, &database, error) ==
CHECK(database && lardon3d_project_db_schema_version(database) == 15); LARDON3D_PROJECT_DB_OK);
CHECK(database && lardon3d_project_db_schema_version(database) == 16);
Lardon3DProjectDbScanSet scanset; Lardon3DProjectDbScanSet scanset;
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) == CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
@ -109,21 +127,24 @@ static bool run_test(void) {
asset_path_for_hash(hash_a, path_a); asset_path_for_hash(hash_a, path_a);
Lardon3DProjectDbImageRegisterStatus identity_status; Lardon3DProjectDbImageRegisterStatus identity_status;
Lardon3DProjectDbImage image_a; Lardon3DProjectDbImage image_a;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_a, path_a, 1, CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_a,
"a.jpg", "/src/a.jpg", 0, 1, &identity_status, path_a, 1, "a.jpg", "/src/a.jpg", 0,
1, &identity_status,
&image_a) == LARDON3D_PROJECT_DB_OK); &image_a) == LARDON3D_PROJECT_DB_OK);
unsigned char hash_b[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xBB}; unsigned char hash_b[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xBB};
char path_b[LARDON3D_PROJECT_DB_PATH_CAPACITY]; char path_b[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(hash_b, path_b); asset_path_for_hash(hash_b, path_b);
Lardon3DProjectDbImage image_b; Lardon3DProjectDbImage image_b;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_b, path_b, 1, CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_b,
"b.jpg", "/src/b.jpg", 0, 2, &identity_status, path_b, 1, "b.jpg", "/src/b.jpg", 0,
2, &identity_status,
&image_b) == LARDON3D_PROJECT_DB_OK); &image_b) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbCandidatePair pair; Lardon3DProjectDbCandidatePair pair;
CHECK(lardon3d_project_db_create_candidate_pair(database, image_a.image_id, image_b.image_id, 10, CHECK(lardon3d_project_db_create_candidate_pair(
&pair) == LARDON3D_PROJECT_DB_OK); database, image_a.image_id, image_b.image_id, 10, &pair) ==
LARDON3D_PROJECT_DB_OK);
CHECK(pair.candidate_pair_id > 0); CHECK(pair.candidate_pair_id > 0);
/* Create feature sets for foreign key references */ /* Create feature sets for foreign key references */
@ -132,8 +153,9 @@ static bool run_test(void) {
feature_asset_path_for_hash(feature_hash_a, feature_path_a); feature_asset_path_for_hash(feature_hash_a, feature_path_a);
Lardon3DProjectDbFeatureSet fs_a; Lardon3DProjectDbFeatureSet fs_a;
CHECK(lardon3d_project_db_register_feature_set( CHECK(lardon3d_project_db_register_feature_set(
database, image_a.image_id, "orb", 1, feature_hash_a, hash_a, 100, 1, 32, database, image_a.image_id, "orb", 1, feature_hash_a, hash_a, 100,
feature_hash_a, feature_path_a, 128, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 10, 1, 32, feature_hash_a, feature_path_a, 128,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 10,
&fs_a) == LARDON3D_PROJECT_DB_OK); &fs_a) == LARDON3D_PROJECT_DB_OK);
CHECK(fs_a.feature_set_id > 0 && fs_a.image_id == image_a.image_id); CHECK(fs_a.feature_set_id > 0 && fs_a.image_id == image_a.image_id);
@ -142,10 +164,10 @@ static bool run_test(void) {
feature_asset_path_for_hash(feature_hash_b, feature_path_b); feature_asset_path_for_hash(feature_hash_b, feature_path_b);
Lardon3DProjectDbFeatureSet fs_b; Lardon3DProjectDbFeatureSet fs_b;
CHECK(lardon3d_project_db_register_feature_set( CHECK(lardon3d_project_db_register_feature_set(
database, image_b.image_id, "sift", 1, feature_hash_b, hash_b, 100, 2, 128, database, image_b.image_id, "sift", 1, feature_hash_b, hash_b, 100,
feature_hash_b, feature_path_b, 256, 2, 128, feature_hash_b, feature_path_b, 256,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 20, &fs_b) == LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 20,
LARDON3D_PROJECT_DB_OK); &fs_b) == LARDON3D_PROJECT_DB_OK);
CHECK(fs_b.feature_set_id > 0 && fs_b.image_id == image_b.image_id); CHECK(fs_b.feature_set_id > 0 && fs_b.image_id == image_b.image_id);
/* Create MATCHED result with inliers */ /* Create MATCHED result with inliers */
@ -156,14 +178,16 @@ static bool run_test(void) {
memset(match_hash, 0x5A, sizeof(match_hash)); memset(match_hash, 0x5A, sizeof(match_hash));
const char *match_path = "assets/matches/5a/asset"; const char *match_path = "assets/matches/5a/asset";
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fs_b.feature_set_id, "orb_bf", 1, fp1,
match_hash, match_path, 392, 100, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
&result) == LARDON3D_PROJECT_DB_OK); 392, 100, &result) == LARDON3D_PROJECT_DB_OK);
CHECK(result.match_result_id > 0 && result.candidate_pair_id == pair.candidate_pair_id && CHECK(result.match_result_id > 0 &&
result.candidate_pair_id == pair.candidate_pair_id &&
result.feature_set_id_a == fs_a.feature_set_id && result.feature_set_id_a == fs_a.feature_set_id &&
result.feature_set_id_b == fs_b.feature_set_id && result.feature_set_id_b == fs_b.feature_set_id &&
strcmp(result.matcher_kind, "orb_bf") == 0 && result.matcher_version == 1 && strcmp(result.matcher_kind, "orb_bf") == 0 &&
result.matcher_version == 1 &&
memcmp(result.parameter_fingerprint, fp1, 32) == 0 && memcmp(result.parameter_fingerprint, fp1, 32) == 0 &&
result.result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED && result.result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
result.match_count == 30 && result.created_at == 100); result.match_count == 30 && result.created_at == 100);
@ -173,9 +197,9 @@ static bool run_test(void) {
unsigned char fp2[32]; unsigned char fp2[32];
memset(fp2, 0x02, 32); memset(fp2, 0x02, 32);
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 2, fp2, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, fs_b.feature_set_id, "orb_bf", 2, fp2,
NULL, NULL, 0, 200, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, NULL, NULL, 0, 200,
&result) == LARDON3D_PROJECT_DB_OK); &result) == LARDON3D_PROJECT_DB_OK);
CHECK(result.result_status == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH && CHECK(result.result_status == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH &&
result.match_count == 0 && !result.has_match_asset); result.match_count == 0 && !result.has_match_asset);
@ -183,10 +207,10 @@ static bool run_test(void) {
/* Duplicate: same CP + same matcher + same params + same FS → CONSTRAINT */ /* Duplicate: same CP + same matcher + same params + same FS → CONSTRAINT */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10, fs_b.feature_set_id, "orb_bf", 1, fp1,
match_hash, match_path, 152, 500, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10, match_hash, match_path,
&result) == LARDON3D_PROJECT_DB_CONSTRAINT); 152, 500, &result) == LARDON3D_PROJECT_DB_CONSTRAINT);
/* Create additional Feature Sets for distinct-FS and inverted-ID tests */ /* Create additional Feature Sets for distinct-FS and inverted-ID tests */
unsigned char feature_hash_a2[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0x33}; unsigned char feature_hash_a2[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0x33};
@ -197,7 +221,8 @@ static bool run_test(void) {
memset(fs_a2_fp, 0xAA, 32); memset(fs_a2_fp, 0xAA, 32);
CHECK(lardon3d_project_db_register_feature_set( CHECK(lardon3d_project_db_register_feature_set(
database, image_a.image_id, "orb", 2, fs_a2_fp, hash_a, 200, 1, 32, database, image_a.image_id, "orb", 2, fs_a2_fp, hash_a, 200, 1, 32,
feature_hash_a2, feature_path_a2, 256, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 30, feature_hash_a2, feature_path_a2, 256,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 30,
&fs_a2) == LARDON3D_PROJECT_DB_OK); &fs_a2) == LARDON3D_PROJECT_DB_OK);
CHECK(fs_a2.feature_set_id > 0 && fs_a2.image_id == image_a.image_id); CHECK(fs_a2.feature_set_id > 0 && fs_a2.image_id == image_a.image_id);
@ -208,10 +233,10 @@ static bool run_test(void) {
unsigned char fs_b2_fp[32]; unsigned char fs_b2_fp[32];
memset(fs_b2_fp, 0xBB, 32); memset(fs_b2_fp, 0xBB, 32);
CHECK(lardon3d_project_db_register_feature_set( CHECK(lardon3d_project_db_register_feature_set(
database, image_b.image_id, "sift", 2, fs_b2_fp, hash_b, 200, 2, 128, database, image_b.image_id, "sift", 2, fs_b2_fp, hash_b, 200, 2,
feature_hash_b2, feature_path_b2, 512, 128, feature_hash_b2, feature_path_b2, 512,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 40, &fs_b2) == LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 40,
LARDON3D_PROJECT_DB_OK); &fs_b2) == LARDON3D_PROJECT_DB_OK);
CHECK(fs_b2.feature_set_id > 0 && fs_b2.image_id == image_b.image_id); CHECK(fs_b2.feature_set_id > 0 && fs_b2.image_id == image_b.image_id);
/* Same CP + same matcher + same params + different FS → distinct */ /* Same CP + same matcher + same params + different FS → distinct */
@ -219,41 +244,46 @@ static bool run_test(void) {
memset(fp5, 0x05, 32); memset(fp5, 0x05, 32);
Lardon3DProjectDbMatchResult distinct_fs_result; Lardon3DProjectDbMatchResult distinct_fs_result;
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a2.feature_set_id, fs_b2.feature_set_id, database, pair.candidate_pair_id, fs_a2.feature_set_id,
"orb_bf", 1, fp5, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 20, fs_b2.feature_set_id, "orb_bf", 1, fp5,
match_hash, match_path, 272, 400, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 20, match_hash, match_path,
&distinct_fs_result) == LARDON3D_PROJECT_DB_OK); 272, 400, &distinct_fs_result) == LARDON3D_PROJECT_DB_OK);
CHECK(distinct_fs_result.feature_set_id_a == fs_a2.feature_set_id && CHECK(distinct_fs_result.feature_set_id_a == fs_a2.feature_set_id &&
distinct_fs_result.feature_set_id_b == fs_b2.feature_set_id); distinct_fs_result.feature_set_id_b == fs_b2.feature_set_id);
uint64_t distinct_id = distinct_fs_result.match_result_id; uint64_t distinct_id = distinct_fs_result.match_result_id;
CHECK(distinct_id != first_id); CHECK(distinct_id != first_id);
/* feature_set_id_a > feature_set_id_b but correct ownership → valid */ /* feature_set_id_a > feature_set_id_b but correct ownership → valid */
/* Register image_c, image_d; create FS for d first, then c → fs_c.id > fs_d.id */ /* Register image_c, image_d; create FS for d first, then c → fs_c.id >
* fs_d.id */
unsigned char hash_c[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xCC}; unsigned char hash_c[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xCC};
char path_c[LARDON3D_PROJECT_DB_PATH_CAPACITY]; char path_c[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(hash_c, path_c); asset_path_for_hash(hash_c, path_c);
Lardon3DProjectDbImage image_c; Lardon3DProjectDbImage image_c;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_c, path_c, 1, CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_c,
"c.jpg", "/src/c.jpg", 0, 3, &identity_status, path_c, 1, "c.jpg", "/src/c.jpg", 0,
&image_c) == LARDON3D_PROJECT_DB_OK); 3, &identity_status,
&image_c) == LARDON3D_PROJECT_DB_OK);
unsigned char hash_d[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xDD}; unsigned char hash_d[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xDD};
char path_d[LARDON3D_PROJECT_DB_PATH_CAPACITY]; char path_d[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(hash_d, path_d); asset_path_for_hash(hash_d, path_d);
Lardon3DProjectDbImage image_d; Lardon3DProjectDbImage image_d;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_d, path_d, 1, CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_d,
"d.jpg", "/src/d.jpg", 0, 4, &identity_status, path_d, 1, "d.jpg", "/src/d.jpg", 0,
&image_d) == LARDON3D_PROJECT_DB_OK); 4, &identity_status,
&image_d) == LARDON3D_PROJECT_DB_OK);
/* Create feature set for image_d first (lower ID), then image_c (higher ID) */ /* Create feature set for image_d first (lower ID), then image_c (higher ID)
*/
unsigned char feature_hash_d[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0x55}; unsigned char feature_hash_d[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0x55};
char feature_path_d[LARDON3D_PROJECT_DB_PATH_CAPACITY]; char feature_path_d[LARDON3D_PROJECT_DB_PATH_CAPACITY];
feature_asset_path_for_hash(feature_hash_d, feature_path_d); feature_asset_path_for_hash(feature_hash_d, feature_path_d);
Lardon3DProjectDbFeatureSet fs_d; Lardon3DProjectDbFeatureSet fs_d;
CHECK(lardon3d_project_db_register_feature_set( CHECK(lardon3d_project_db_register_feature_set(
database, image_d.image_id, "orb", 1, feature_hash_d, hash_d, 100, 1, 32, database, image_d.image_id, "orb", 1, feature_hash_d, hash_d, 100,
feature_hash_d, feature_path_d, 128, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 50, 1, 32, feature_hash_d, feature_path_d, 128,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 50,
&fs_d) == LARDON3D_PROJECT_DB_OK); &fs_d) == LARDON3D_PROJECT_DB_OK);
unsigned char feature_hash_c[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0x66}; unsigned char feature_hash_c[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0x66};
@ -261,26 +291,27 @@ static bool run_test(void) {
feature_asset_path_for_hash(feature_hash_c, feature_path_c); feature_asset_path_for_hash(feature_hash_c, feature_path_c);
Lardon3DProjectDbFeatureSet fs_c; Lardon3DProjectDbFeatureSet fs_c;
CHECK(lardon3d_project_db_register_feature_set( CHECK(lardon3d_project_db_register_feature_set(
database, image_c.image_id, "sift", 1, feature_hash_c, hash_c, 100, 2, 128, database, image_c.image_id, "sift", 1, feature_hash_c, hash_c, 100,
feature_hash_c, feature_path_c, 256, 2, 128, feature_hash_c, feature_path_c, 256,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 60, &fs_c) == LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 60,
LARDON3D_PROJECT_DB_OK); &fs_c) == LARDON3D_PROJECT_DB_OK);
/* fs_c was created after fs_d → fs_c.feature_set_id > fs_d.feature_set_id */ /* fs_c was created after fs_d → fs_c.feature_set_id > fs_d.feature_set_id */
CHECK(fs_c.feature_set_id > fs_d.feature_set_id); CHECK(fs_c.feature_set_id > fs_d.feature_set_id);
/* CP (image_c, image_d): image_c should come first since registered first */ /* CP (image_c, image_d): image_c should come first since registered first */
Lardon3DProjectDbCandidatePair pair_cd; Lardon3DProjectDbCandidatePair pair_cd;
CHECK(lardon3d_project_db_create_candidate_pair(database, image_c.image_id, image_d.image_id, 20, CHECK(lardon3d_project_db_create_candidate_pair(
&pair_cd) == LARDON3D_PROJECT_DB_OK); database, image_c.image_id, image_d.image_id, 20, &pair_cd) ==
LARDON3D_PROJECT_DB_OK);
unsigned char fp6[32]; unsigned char fp6[32];
memset(fp6, 0x06, 32); memset(fp6, 0x06, 32);
Lardon3DProjectDbMatchResult inverted_fs_result; Lardon3DProjectDbMatchResult inverted_fs_result;
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair_cd.candidate_pair_id, fs_c.feature_set_id, fs_d.feature_set_id, database, pair_cd.candidate_pair_id, fs_c.feature_set_id,
"orb_bf", 1, fp6, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 15, fs_d.feature_set_id, "orb_bf", 1, fp6,
match_hash, match_path, 212, 500, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 15, match_hash, match_path,
&inverted_fs_result) == LARDON3D_PROJECT_DB_OK); 212, 500, &inverted_fs_result) == LARDON3D_PROJECT_DB_OK);
CHECK(inverted_fs_result.feature_set_id_a == fs_c.feature_set_id && CHECK(inverted_fs_result.feature_set_id_a == fs_c.feature_set_id &&
inverted_fs_result.feature_set_id_b == fs_d.feature_set_id); inverted_fs_result.feature_set_id_b == fs_d.feature_set_id);
CHECK(fs_c.feature_set_id > fs_d.feature_set_id); CHECK(fs_c.feature_set_id > fs_d.feature_set_id);
@ -289,26 +320,27 @@ static bool run_test(void) {
unsigned char fp7[32]; unsigned char fp7[32];
memset(fp7, 0x07, 32); memset(fp7, 0x07, 32);
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_b.feature_set_id, fs_a.feature_set_id, database, pair.candidate_pair_id, fs_b.feature_set_id,
"orb_bf", 1, fp7, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5, fs_a.feature_set_id, "orb_bf", 1, fp7,
match_hash, match_path, 92, 600, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5, match_hash, match_path, 92,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 600, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* FeatureSet B belonging to image A (not image B) → rejected */ /* FeatureSet B belonging to image A (not image B) → rejected */
unsigned char fp8[32]; unsigned char fp8[32];
memset(fp8, 0x08, 32); memset(fp8, 0x08, 32);
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_a2.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp8, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5, fs_a2.feature_set_id, "orb_bf", 1, fp8,
match_hash, match_path, 92, 700, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5, match_hash, match_path, 92,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 700, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Load by ID */ /* Load by ID */
Lardon3DProjectDbMatchResult loaded; Lardon3DProjectDbMatchResult loaded;
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) == CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(loaded.match_result_id == first_id && loaded.result_status == CHECK(loaded.match_result_id == first_id &&
LARDON3D_MATCH_RESULT_STATUS_MATCHED && loaded.match_count == 30); loaded.result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
loaded.match_count == 30);
/* Load not found */ /* Load not found */
CHECK(lardon3d_project_db_load_match_result(database, 999999, &loaded) == CHECK(lardon3d_project_db_load_match_result(database, 999999, &loaded) ==
@ -316,50 +348,52 @@ static bool run_test(void) {
/* Find by full 6-part identity */ /* Find by full 6-part identity */
Lardon3DProjectDbMatchResult found; Lardon3DProjectDbMatchResult found;
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id, CHECK(lardon3d_project_db_find_match_result(
fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, fs_b.feature_set_id, "orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_OK); &found) == LARDON3D_PROJECT_DB_OK);
CHECK(found.match_result_id == first_id && found.match_count == 30); CHECK(found.match_result_id == first_id && found.match_count == 30);
/* Find with wrong feature_set_id → NOT_FOUND */ /* Find with wrong feature_set_id → NOT_FOUND */
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id, CHECK(lardon3d_project_db_find_match_result(
fs_a2.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a2.feature_set_id,
"orb_bf", 1, fp1, fs_b.feature_set_id, "orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_NOT_FOUND); &found) == LARDON3D_PROJECT_DB_NOT_FOUND);
/* Find not found */ /* Find not found */
unsigned char unknown_fp[32]; unsigned char unknown_fp[32];
memset(unknown_fp, 0xFF, 32); memset(unknown_fp, 0xFF, 32);
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id, CHECK(lardon3d_project_db_find_match_result(
fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, unknown_fp, fs_b.feature_set_id, "orb_bf", 1, unknown_fp,
&found) == LARDON3D_PROJECT_DB_NOT_FOUND); &found) == LARDON3D_PROJECT_DB_NOT_FOUND);
/* Find with wrong candidate_pair */ /* Find with wrong candidate_pair */
CHECK(lardon3d_project_db_find_match_result(database, 999999, CHECK(lardon3d_project_db_find_match_result(
fs_a.feature_set_id, fs_b.feature_set_id, database, 999999, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, "orb_bf", 1, fp1, &found) == LARDON3D_PROJECT_DB_NOT_FOUND);
&found) == LARDON3D_PROJECT_DB_NOT_FOUND);
/* Pagination */ /* Pagination */
Lardon3DProjectDbMatchResult page[LARDON3D_PROJECT_DB_MATCH_RESULT_PAGE_MAX]; Lardon3DProjectDbMatchResult page[LARDON3D_PROJECT_DB_MATCH_RESULT_PAGE_MAX];
size_t page_count = 0; size_t page_count = 0;
CHECK(lardon3d_project_db_list_match_results(database, 0, page, 4, &page_count) == CHECK(lardon3d_project_db_list_match_results(
LARDON3D_PROJECT_DB_OK); database, 0, page, 4, &page_count) == LARDON3D_PROJECT_DB_OK);
CHECK(page_count == 4 && page[0].match_result_id == first_id && CHECK(page_count == 4 && page[0].match_result_id == first_id &&
page[1].match_result_id == second_id && page[2].match_result_id == distinct_id); page[1].match_result_id == second_id &&
page[2].match_result_id == distinct_id);
/* Cursor pagination */ /* Cursor pagination */
page_count = 0; page_count = 0;
CHECK(lardon3d_project_db_list_match_results(database, first_id, page, 2, &page_count) == CHECK(lardon3d_project_db_list_match_results(database, first_id, page, 2,
&page_count) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(page_count == 2 && page[0].match_result_id == second_id && CHECK(page_count == 2 && page[0].match_result_id == second_id &&
page[1].match_result_id == distinct_id); page[1].match_result_id == distinct_id);
/* End of list */ /* End of list */
page_count = 0; page_count = 0;
CHECK(lardon3d_project_db_list_match_results(database, distinct_id, page, 1, &page_count) == CHECK(lardon3d_project_db_list_match_results(database, distinct_id, page, 1,
&page_count) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(page_count == 1 && page[0].match_result_id > distinct_id); CHECK(page_count == 1 && page[0].match_result_id > distinct_id);
@ -369,114 +403,116 @@ static bool run_test(void) {
&page_count) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &page_count) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: NULL database */ /* Invalid arguments: NULL database */
CHECK(lardon3d_project_db_create_match_result(NULL, pair.candidate_pair_id, CHECK(lardon3d_project_db_create_match_result(
fs_a.feature_set_id, fs_b.feature_set_id, "orb_bf", 1, fp1, NULL, pair.candidate_pair_id, fs_a.feature_set_id,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fs_b.feature_set_id, "orb_bf", 1, fp1,
match_hash, match_path, 392, 100, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: 0 ids */ /* Invalid arguments: 0 ids */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, 0, fs_a.feature_set_id, fs_b.feature_set_id, "orb_bf", 1, fp1, database, 0, fs_a.feature_set_id, fs_b.feature_set_id, "orb_bf", 1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash,
match_hash, match_path, 392, 100, &result) == match_path, 392, 100,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, 0, fs_b.feature_set_id, "orb_bf", 1, fp1, database, pair.candidate_pair_id, 0, fs_b.feature_set_id, "orb_bf",
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash,
match_hash, match_path, 392, 100, &result) == match_path, 392, 100,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: NULL output */ /* Invalid arguments: NULL output */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fs_b.feature_set_id, "orb_bf", 1, fp1,
match_hash, match_path, 392, 100, NULL) == LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 392, 100, NULL) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: empty matcher_kind */ /* Invalid arguments: empty matcher_kind */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fs_b.feature_set_id, "", 1, fp1,
match_hash, match_path, 392, 100, &result) == LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: 0 matcher_version */ /* Invalid arguments: 0 matcher_version */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 0, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fs_b.feature_set_id, "orb_bf", 0, fp1,
match_hash, match_path, 392, 100, &result) == LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: NULL fingerprint */ /* Invalid arguments: NULL fingerprint */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, NULL, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fs_b.feature_set_id, "orb_bf", 1, NULL,
match_hash, match_path, 392, 100, &result) == LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: bad status */ /* Invalid arguments: bad status */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, 2, 30, fs_b.feature_set_id, "orb_bf", 1, fp1, 2, 30, match_hash,
match_hash, match_path, 392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); match_path, 392, 100,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid result/asset combinations */ /* Invalid result/asset combinations */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10, fs_b.feature_set_id, "orb_bf", 1, fp1,
NULL, NULL, 0, 100, &result) == LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10, NULL, NULL, 0, 100,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"bad-no-match", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, fs_b.feature_set_id, "bad-no-match", 1, fp1,
match_hash, match_path, 32, 100, &result) == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, match_hash, match_path,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 32, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"too-many", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 8193, fs_b.feature_set_id, "too-many", 1, fp1,
match_hash, match_path, 98348, 100, &result) == LARDON3D_MATCH_RESULT_STATUS_MATCHED, 8193, match_hash, match_path,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 98348, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: negative created_at */ /* Invalid arguments: negative created_at */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, fs_b.feature_set_id, "orb_bf", 1, fp1,
match_hash, match_path, 392, -1, &result) == LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
LARDON3D_PROJECT_DB_INVALID_ARGUMENT); 392, -1, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Status boundary values */ /* Status boundary values */
CHECK(lardon3d_project_db_create_match_result( CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"boundary", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, fs_b.feature_set_id, "boundary", 1, fp1,
NULL, NULL, 0, 500, &result) == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, NULL, NULL, 0, 500,
LARDON3D_PROJECT_DB_OK); &result) == LARDON3D_PROJECT_DB_OK);
/* Close/reopen persistence */ /* Close/reopen persistence */
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
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) ==
CHECK(lardon3d_project_db_schema_version(database) == 15); LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 16);
/* 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) ==
LARDON3D_PROJECT_DB_OK); LARDON3D_PROJECT_DB_OK);
CHECK(loaded.match_result_id == first_id && loaded.result_status == CHECK(loaded.match_result_id == first_id &&
LARDON3D_MATCH_RESULT_STATUS_MATCHED && loaded.result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
strcmp(loaded.matcher_kind, "orb_bf") == 0 && loaded.match_count == 30); strcmp(loaded.matcher_kind, "orb_bf") == 0 && loaded.match_count == 30);
/* Verify persistence: find by key */ /* Verify persistence: find by key */
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id, CHECK(lardon3d_project_db_find_match_result(
fs_a.feature_set_id, fs_b.feature_set_id, database, pair.candidate_pair_id, fs_a.feature_set_id,
"orb_bf", 1, fp1, fs_b.feature_set_id, "orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_OK); &found) == LARDON3D_PROJECT_DB_OK);
CHECK(found.match_result_id == first_id); CHECK(found.match_result_id == first_id);
/* Verify persistence of distinct result */ /* Verify persistence of distinct result */
Lardon3DProjectDbMatchResult distinct_loaded; Lardon3DProjectDbMatchResult distinct_loaded;
CHECK(lardon3d_project_db_load_match_result(database, distinct_id, &distinct_loaded) == CHECK(lardon3d_project_db_load_match_result(
LARDON3D_PROJECT_DB_OK); database, distinct_id, &distinct_loaded) == LARDON3D_PROJECT_DB_OK);
CHECK(distinct_loaded.match_result_id == distinct_id && CHECK(distinct_loaded.match_result_id == distinct_id &&
distinct_loaded.feature_set_id_a == fs_a2.feature_set_id && distinct_loaded.feature_set_id_a == fs_a2.feature_set_id &&
distinct_loaded.feature_set_id_b == fs_b2.feature_set_id); distinct_loaded.feature_set_id_b == fs_b2.feature_set_id);
@ -484,8 +520,9 @@ static bool run_test(void) {
/* Verify inverted result persisted */ /* Verify inverted result persisted */
Lardon3DProjectDbMatchResult inverted_loaded; Lardon3DProjectDbMatchResult inverted_loaded;
CHECK(lardon3d_project_db_load_match_result(database, inverted_fs_result.match_result_id, CHECK(lardon3d_project_db_load_match_result(
&inverted_loaded) == LARDON3D_PROJECT_DB_OK); database, inverted_fs_result.match_result_id, &inverted_loaded) ==
LARDON3D_PROJECT_DB_OK);
CHECK(inverted_loaded.feature_set_id_a == fs_c.feature_set_id && CHECK(inverted_loaded.feature_set_id_a == fs_c.feature_set_id &&
inverted_loaded.feature_set_id_b == fs_d.feature_set_id); inverted_loaded.feature_set_id_b == fs_d.feature_set_id);
CHECK(inverted_loaded.feature_set_id_a > inverted_loaded.feature_set_id_b); CHECK(inverted_loaded.feature_set_id_a > inverted_loaded.feature_set_id_b);
@ -495,15 +532,20 @@ static bool run_test(void) {
/* Migration from v9 */ /* Migration from v9 */
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(
CHECK(lardon3d_project_db_open(v9_path, &database, error) == LARDON3D_PROJECT_DB_OK); v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
CHECK(lardon3d_project_db_schema_version(database) == 15); CHECK(lardon3d_project_db_open(v9_path, &database, error) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 16);
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'", 15)); CHECK(query_integer(
CHECK(query_integer(v9_path, v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " CHECK(
"name='match_results'", 1)); query_integer(v9_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'",
1));
/* Forced migration failure v10 */ /* Forced migration failure v10 */
CHECK(create_v9_database(failed_v10_path)); CHECK(create_v9_database(failed_v10_path));
@ -511,15 +553,22 @@ static bool run_test(void) {
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) ==
LARDON3D_PROJECT_DB_IO_ERROR); LARDON3D_PROJECT_DB_IO_ERROR);
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V10") == 0); CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V10") == 0);
CHECK(query_integer(failed_v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
CHECK(query_integer(failed_v10_path, CHECK(query_integer(failed_v10_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " "SELECT value FROM metadata WHERE key='schema_version'",
"name='match_results'", 0)); 9));
CHECK(query_integer(failed_v10_path, CHECK(
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " query_integer(failed_v10_path,
"name='matcher_tasks'", 0)); "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) == LARDON3D_PROJECT_DB_OK && "name='match_results'",
lardon3d_project_db_schema_version(database) == 15); 0));
CHECK(
query_integer(failed_v10_path,
"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) == 16);
lardon3d_project_db_close(database); lardon3d_project_db_close(database);
database = NULL; database = NULL;
@ -530,6 +579,4 @@ static bool run_test(void) {
return true; return true;
} }
int main(void) { int main(void) { return run_test() ? EXIT_SUCCESS : EXIT_FAILURE; }
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
}

View file

@ -63,12 +63,12 @@ static bool query_integer(const char *path, const char *sql,
sqlite3_int64 expected) { sqlite3_int64 expected) {
sqlite3 *connection = NULL; sqlite3 *connection = NULL;
sqlite3_stmt *statement = NULL; sqlite3_stmt *statement = NULL;
bool success = sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, bool success =
NULL) == SQLITE_OK && sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) ==
sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) == SQLITE_OK &&
SQLITE_OK && sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW && sqlite3_step(statement) == SQLITE_ROW &&
sqlite3_column_int64(statement, 0) == expected; sqlite3_column_int64(statement, 0) == expected;
if (statement) { if (statement) {
(void)sqlite3_finalize(statement); (void)sqlite3_finalize(statement);
} }
@ -89,12 +89,20 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
} }
static const char sql[] = static const char sql[] =
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;" "PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
"DROP TABLE IF EXISTS sparse_landmark_observations;"
"DROP TABLE IF EXISTS sparse_landmarks;"
"DROP TABLE IF EXISTS sparse_registered_images;"
"DROP TABLE IF EXISTS sparse_reconstruction_components;"
"DROP TABLE IF EXISTS sparse_reconstructions;"
"DROP TABLE IF EXISTS sparse_calibration_scope_images;"
"DROP TABLE IF EXISTS sparse_calibration_scopes;"
"DROP TABLE IF EXISTS sparse_calibrations;"
"DROP TABLE geometric_verifier_tasks;" "DROP TABLE geometric_verifier_tasks;"
"DROP TABLE geometric_verification_results;" "DROP TABLE geometric_verification_results;"
"DROP TABLE matcher_tasks;" "DROP TABLE matcher_tasks;"
"DROP TABLE track_observations;" "DROP TABLE track_observations;"
"DROP TABLE tracks;" "DROP TABLE tracks;"
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;" "DROP TABLE track_sets;DROP TABLE track_builder_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;";
bool success = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK; bool success = sqlite3_exec(connection, sql, NULL, NULL, NULL) == SQLITE_OK;
@ -346,20 +354,23 @@ static bool run_test(void) {
CHECK(query_integer(database_path, CHECK(query_integer(database_path,
"SELECT value FROM metadata WHERE key='schema_version'", "SELECT value FROM metadata WHERE key='schema_version'",
10)); 10));
CHECK(query_integer(database_path, CHECK(
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " query_integer(database_path,
"name='match_results'", "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
1)); "name='match_results'",
CHECK(query_integer(database_path, 1));
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " CHECK(
"name='matcher_tasks'", query_integer(database_path,
0)); "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'",
0));
CHECK(reopen_runtime(&fixture)); CHECK(reopen_runtime(&fixture));
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 15); CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 16);
CHECK(query_integer(database_path, CHECK(
"SELECT count(*) FROM sqlite_master WHERE type='table' AND " query_integer(database_path,
"name='matcher_tasks'", "SELECT count(*) FROM sqlite_master WHERE type='table' AND "
1)); "name='matcher_tasks'",
1));
const Lardon3DTaskKindRegistry *registry = const Lardon3DTaskKindRegistry *registry =
lardon3d_task_kind_registry_production(); lardon3d_task_kind_registry_production();
const Lardon3DTaskKindDescriptor *descriptor = NULL; const Lardon3DTaskKindDescriptor *descriptor = NULL;

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,313 @@
#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>
#include <lardon3d/sparse_sfm_model.h>
static bool exec_sql(const char *path, const char *sql) {
sqlite3 *connection = NULL;
bool ok = sqlite3_open(path, &connection) == SQLITE_OK;
if (ok)
ok = sqlite3_exec(connection, "PRAGMA foreign_keys=ON", NULL, NULL, NULL) ==
SQLITE_OK;
char *message = NULL;
if (ok)
ok = sqlite3_exec(connection, sql, NULL, NULL, &message) == SQLITE_OK;
if (!ok && connection)
fprintf(stderr, "resource SQL failed: %s\n",
message ? message : sqlite3_errmsg(connection));
sqlite3_free(message);
if (connection)
ok = sqlite3_close(connection) == SQLITE_OK && ok;
return ok;
}
static bool seed_upstream(const char *path, uint64_t landmark_count) {
char *sql = NULL;
int required = snprintf(
NULL, 0,
"PRAGMA foreign_keys=ON;"
"INSERT INTO scansets(scanset_id,name,created_at,updated_at) "
"VALUES(1,'resource',1,1);"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) "
"SELECT x,randomblob(32),printf('resource/image-%%d',x),1,1,1 FROM n;"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO images(image_id,scanset_id,asset_id,original_name,source_path,"
"producer_task_id,imported_at) SELECT x,1,x,printf('image-%%d',x),"
"printf('resource/image-%%d',x),NULL,1 FROM n;"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO feature_assets(feature_asset_id,sha256,path,size_bytes,durability,created_at) "
"SELECT x,randomblob(32),printf('resource/feature-%%d',x),1,0,1 FROM n;"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO feature_sets(feature_set_id,image_id,feature_asset_id,extractor_kind,"
"extractor_version,parameter_fingerprint,source_image_sha256,feature_count,"
"descriptor_type,descriptor_dimension,producer_task_id,created_at) SELECT x,x,x,"
"'resource',1,randomblob(32),randomblob(32),5,1,32,NULL,1 FROM n;"
"INSERT INTO track_sets(track_set_id,builder_kind,builder_version,parameter_fingerprint,"
"verifier_kind,verifier_version,verifier_fingerprint,input_scope_hash,gvr_count,"
"track_count,created_at) VALUES(1,'resource',1,zeroblob(32),1,1,zeroblob(32),"
"zeroblob(32),1,%llu,1);"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<%llu) "
"INSERT INTO tracks(track_id,track_set_id,observation_count) SELECT x,1,5 FROM n;"
"WITH RECURSIVE t(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM t WHERE x<%llu), "
"p(x) AS (SELECT 0 UNION ALL SELECT x+1 FROM p WHERE x<4) "
"INSERT INTO track_observations(track_set_id,track_id,feature_set_id,feature_index,"
"position_in_track) SELECT 1,t.x,CASE WHEN t.x<=%llu THEN 1+((((t.x-1)%%50)*5+p.x)%%250) "
"ELSE 251+((((t.x-1-%llu)%%50)*5+p.x)%%250) END,CASE WHEN t.x<=%llu "
"THEN ((t.x-1)/50) ELSE ((t.x-1-%llu)/50) END,p.x "
"FROM t CROSS JOIN p;",
(unsigned long long)landmark_count,
(unsigned long long)landmark_count,
(unsigned long long)landmark_count,
(unsigned long long)(landmark_count / 2),
(unsigned long long)(landmark_count / 2),
(unsigned long long)(landmark_count / 2),
(unsigned long long)(landmark_count / 2));
if (required < 0)
return false;
sql = malloc((size_t)required + 1);
if (!sql)
return false;
(void)snprintf(
sql, (size_t)required + 1,
"PRAGMA foreign_keys=ON;"
"INSERT INTO scansets(scanset_id,name,created_at,updated_at) VALUES(1,'resource',1,1);"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) "
"SELECT x,randomblob(32),printf('resource/image-%%d',x),1,1,1 FROM n;"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO images(image_id,scanset_id,asset_id,original_name,source_path,producer_task_id,imported_at) "
"SELECT x,1,x,printf('image-%%d',x),printf('resource/image-%%d',x),NULL,1 FROM n;"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO feature_assets(feature_asset_id,sha256,path,size_bytes,durability,created_at) "
"SELECT x,randomblob(32),printf('resource/feature-%%d',x),1,0,1 FROM n;"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<500) "
"INSERT INTO feature_sets(feature_set_id,image_id,feature_asset_id,"
"extractor_kind,extractor_version,parameter_fingerprint,source_image_sha256,"
"feature_count,descriptor_type,descriptor_dimension,producer_task_id,created_at) "
"SELECT x,x,x,'resource',1,randomblob(32),randomblob(32),5,1,32,NULL,1 FROM n;"
"INSERT INTO track_sets(track_set_id,builder_kind,builder_version,"
"parameter_fingerprint,verifier_kind,verifier_version,"
"verifier_fingerprint,input_scope_hash,gvr_count,track_count,created_at) "
"VALUES(1,'resource',1,zeroblob(32),1,1,zeroblob(32),zeroblob(32),1,%llu,1);"
"WITH RECURSIVE n(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM n WHERE x<%llu) "
"INSERT INTO tracks(track_id,track_set_id,observation_count) SELECT x,1,5 FROM n;"
"WITH RECURSIVE t(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM t WHERE x<%llu),"
"p(x) AS (SELECT 0 UNION ALL SELECT x+1 FROM p WHERE x<4) "
"INSERT INTO track_observations(track_set_id,track_id,feature_set_id,"
"feature_index,position_in_track) SELECT 1,t.x,CASE WHEN t.x<=%llu "
"THEN 1+((((t.x-1)%%50)*5+p.x)%%250) ELSE "
"251+((((t.x-1-%llu)%%50)*5+p.x)%%250) END,CASE WHEN t.x<=%llu "
"THEN ((t.x-1)/50) ELSE ((t.x-1-%llu)/50) END,p.x FROM t CROSS JOIN p;",
(unsigned long long)landmark_count,
(unsigned long long)landmark_count,
(unsigned long long)landmark_count,
(unsigned long long)(landmark_count / 2),
(unsigned long long)(landmark_count / 2),
(unsigned long long)(landmark_count / 2),
(unsigned long long)(landmark_count / 2));
bool ok = exec_sql(path, sql);
free(sql);
return ok;
}
static bool publish_fixture(const char *path, uint64_t landmark_count) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *db = NULL;
if (lardon3d_project_db_open(path, &db, error) != LARDON3D_PROJECT_DB_OK)
return false;
Lardon3DSparseCalibration calibration = {
.model_kind = 1, .model_version = 1, .width = 4000, .height = 3000,
.fx = 2000, .fy = 2000, .cx = 2000, .cy = 1500, .provenance_kind = 1};
Lardon3DSparseCalibration stored;
if (lardon3d_sparse_calibration_create(db, &calibration, &stored) !=
LARDON3D_PROJECT_DB_OK)
return false;
Lardon3DSparseCalibrationMember *members = calloc(500, sizeof(*members));
for (size_t index = 0; index < 500; ++index) {
members[index].image_id = index + 1;
members[index].calibration_id = stored.calibration_id;
memcpy(members[index].calibration_hash, stored.scientific_hash, 32);
}
Lardon3DSparseCalibrationScope scope;
bool ok = lardon3d_sparse_calibration_scope_create(db, members, 500, &scope) ==
LARDON3D_PROJECT_DB_OK;
free(members);
if (!ok)
fprintf(stderr, "scope or allocation failed\n");
if (!ok)
return false;
Lardon3DSparseComponent components[2] = {
{.component_key = 1, .registered_image_count = 250,
.landmark_count = landmark_count / 2},
{.component_key = 251, .registered_image_count = 250,
.landmark_count = landmark_count / 2}};
Lardon3DSparseRegisteredImage *poses = calloc(500, sizeof(*poses));
Lardon3DSparseLandmark *landmarks = calloc(landmark_count, sizeof(*landmarks));
Lardon3DSparseLandmarkObservation *observations =
calloc(landmark_count * 5, sizeof(*observations));
if (!poses || !landmarks || !observations)
return false;
for (size_t index = 0; index < 500; ++index)
poses[index] = (Lardon3DSparseRegisteredImage){
.image_id = index + 1,
.component_key = index < 250 ? 1 : 251,
.rotation_cw = {1, 0, 0, 0, 1, 0, 0, 0, 1},
.translation_cw = {(double)index, 0, 0}};
for (uint64_t track = 0; track < landmark_count; ++track) {
size_t component_base = track < landmark_count / 2 ? 0 : 250;
uint64_t local_track = track < landmark_count / 2
? track
: track - landmark_count / 2;
uint64_t component_key = component_base ? 251 : 1;
landmarks[track] = (Lardon3DSparseLandmark){
.track_id = track + 1, .component_key = component_key, .x = 0,
.y = 0, .z = 1, .reprojection_rmse_px = 0.1,
.reprojection_median_px = 0.1, .observation_count = 5};
for (uint32_t position = 0; position < 5; ++position) {
size_t offset = (size_t)track * 5 + position;
uint64_t feature_set = (uint64_t)component_base +
((local_track % 50) * 5 + position) % 250 + 1;
observations[offset] = (Lardon3DSparseLandmarkObservation){
.track_id = track + 1,
.feature_set_id = feature_set,
.feature_index = (uint32_t)(local_track / 50),
.position_in_track = position};
}
}
Lardon3DSparsePublication publication = {
.track_set_id = 1,
.calibration_scope_id = scope.scope_id,
.sfm_kind = 1,
.sfm_version = 1,
.components = components,
.component_count = 2,
.registered_images = poses,
.registered_image_count = 500,
.landmarks = landmarks,
.landmark_count = landmark_count,
.observations = observations,
.observation_count = landmark_count * 5,
.reprojection_rmse_px = 0.1,
.reprojection_median_px = 0.1,
.created_at = 1};
publication.parameter_fingerprint[0] = 91;
Lardon3DSparseReconstruction reconstruction;
Lardon3DProjectDbResult publication_result =
lardon3d_sparse_reconstruction_publish(db, &publication, &reconstruction);
ok = publication_result == LARDON3D_PROJECT_DB_OK;
if (!ok) {
char detail[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
(void)lardon3d_project_db_last_error(db, detail);
fprintf(stderr, "publish result %d: %s\n", publication_result, detail);
}
if (ok)
printf("reconstruction_id=%llu landmarks=%llu observations=%llu\n",
(unsigned long long)reconstruction.reconstruction_id,
(unsigned long long)landmark_count,
(unsigned long long)(landmark_count * 5));
free(poses);
free(landmarks);
free(observations);
lardon3d_project_db_close(db);
return ok;
}
static bool page_fixture(const char *path, uint64_t reconstruction_id,
unsigned passes) {
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *db = NULL;
if (lardon3d_project_db_open(path, &db, error) != LARDON3D_PROJECT_DB_OK)
return false;
uint64_t totals[4] = {0};
for (unsigned pass = 0; pass < passes; ++pass) {
Lardon3DSparseComponent components_items[64];
Lardon3DSparseComponentPage components = {.items = components_items};
uint64_t cursor = 0;
while (lardon3d_sparse_component_list(db, reconstruction_id, cursor, 64,
&components) == LARDON3D_PROJECT_DB_OK &&
components.count) {
totals[0] += components.count;
cursor = components.next_component_key;
}
Lardon3DSparseRegisteredImage cameras[64];
Lardon3DSparseRegisteredImagePage camera_page = {.items = cameras};
cursor = 0;
while (lardon3d_sparse_registered_image_list(
db, reconstruction_id, cursor, 64, &camera_page) ==
LARDON3D_PROJECT_DB_OK &&
camera_page.count) {
totals[1] += camera_page.count;
cursor = camera_page.next_image_id;
}
Lardon3DSparseLandmark landmarks[64];
Lardon3DSparseLandmarkPage landmark_page = {.items = landmarks};
cursor = 0;
while (lardon3d_sparse_landmark_list(db, reconstruction_id, cursor, 64,
&landmark_page) ==
LARDON3D_PROJECT_DB_OK &&
landmark_page.count) {
totals[2] += landmark_page.count;
cursor = landmark_page.next_track_id;
}
Lardon3DSparseLandmarkObservation observations[64];
Lardon3DSparseObservationPage observation_page = {.items = observations};
uint64_t landmark_cursor = 0;
uint32_t position_cursor = 0;
while (lardon3d_sparse_observation_list(
db, reconstruction_id, landmark_cursor, position_cursor, 64,
&observation_page) == LARDON3D_PROJECT_DB_OK &&
observation_page.count) {
totals[3] += observation_page.count;
landmark_cursor = observation_page.next_landmark_id;
position_cursor = observation_page.next_position_in_track;
}
}
printf("passes=%u components=%llu cameras=%llu landmarks=%llu observations=%llu\n",
passes, (unsigned long long)totals[0], (unsigned long long)totals[1],
(unsigned long long)totals[2], (unsigned long long)totals[3]);
lardon3d_project_db_close(db);
return true;
}
int main(int argc, char **argv) {
if (argc < 3)
return 2;
const char *mode = argv[1];
const char *path = argv[2];
if (strcmp(mode, "publish") == 0) {
uint64_t landmarks = argc > 3 ? strtoull(argv[3], NULL, 10) : 100000;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
Lardon3DProjectDb *database = NULL;
if (lardon3d_project_db_open(path, &database, error) !=
LARDON3D_PROJECT_DB_OK ||
!database) {
fprintf(stderr, "open resource database failed: %s\n", error);
return 1;
}
lardon3d_project_db_close(database);
if (!seed_upstream(path, landmarks)) {
fprintf(stderr, "seed upstream failed\n");
return 1;
}
if (!publish_fixture(path, landmarks)) {
fprintf(stderr, "publish fixture failed\n");
return 1;
}
return 0;
}
if (strcmp(mode, "page") == 0) {
unsigned passes = argc > 4 ? (unsigned)strtoul(argv[4], NULL, 10) : 5;
return page_fixture(path, argc > 3 ? strtoull(argv[3], NULL, 10) : 1,
passes)
? 0
: 1;
}
return 2;
}