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.
**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.
## Extension v2 multi-descriptor

View file

@ -1,6 +1,8 @@
# 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
> migration transactionnelle v13→v14 ajoute
> 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
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
### Entités principales
@ -758,6 +775,12 @@ sont documentés dans `tracks.md`.
**IMPLEMENTED** — Track Builder v1 durable : table `track_builder_tasks`, scope
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
source `project_db.c` exposent `create_track_set`, `load_track_set`,
`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
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
incremental, la triangulation candidate, le gauge, les conventions de pose,
les limites BA et l'enveloppe matérielle sont documentés dans
`architecture/sparse_sfm.md`. La production Sparse SfM reste
**NOT_IMPLEMENTED** jusqu'aux Gates BG.
`architecture/sparse_sfm.md`. Le solveur Sparse SfM reste
**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.
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
**GATE A — DECISION after contract and probe study.** This document defines the
scientific and architectural contract for the future Sparse SfM layer. It does
not implement a solver, change Track Model v1, change Track Builder v1, add a
Project DB v16, or add a Task Kind. `FACT`, `CANDIDATE`, `DECISION` and `FROZEN`
remain explicit: the upstream Track Model and Track Builder are FROZEN; the
choices below are Gate A decisions for the next implementation gates, not a
claim that Sparse SfM is implemented.
scientific and architectural contract for the Sparse SfM layer. B2 implements
the immutable v16 persistence model and its bounded readers; it does not
implement a numerical solver, change Track Model v1, change Track Builder v1,
or add a Task Kind. `FACT`, `CANDIDATE`, `DECISION` and `FROZEN` remain explicit:
the upstream Track Model and Track Builder are FROZEN, while numerical Sparse
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
@ -383,3 +388,140 @@ the synthetic ground-truth and sparse-solver gates.
No production Sparse SfM, triangulator, camera solver, BA, Project DB v16,
metric alignment, control-point scale, dense/MVS, mesh, texturing, Vulkan SfM,
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>
enum {
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 15,
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 16,
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
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/image_view.c',
'src/project.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/task_checkpoint.c',
'src/task_kind_registry.c',
@ -172,7 +172,7 @@ import_test = executable(
'src/app_state.c',
'src/import.c',
'src/image_catalog_persistent.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -191,7 +191,7 @@ import_task_test = executable(
'src/import.c',
'src/import_task.c',
'src/project.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/task_checkpoint.c',
'src/task_kind_registry.c',
@ -229,7 +229,7 @@ image_catalog_test = executable(
'src/app_state.c',
'src/image_catalog.c',
'src/image_catalog_persistent.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -246,7 +246,7 @@ persistent_image_catalog_test = executable(
'tests/test_persistent_image_catalog.c',
'src/app_state.c',
'src/image_catalog_persistent.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -266,7 +266,7 @@ feature_store_test = executable(
'src/image_catalog_persistent.c',
'src/feature_extractor_opencv.cpp',
'src/feature_store.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -286,7 +286,7 @@ feature_file_robustness_test = executable(
'src/image_catalog_persistent.c',
'src/feature_extractor_opencv.cpp',
'src/feature_store.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -304,7 +304,7 @@ visual_index_test = executable(
'tests/test_visual_index.c', 'src/app_state.c',
'src/image_catalog_persistent.c', 'src/feature_store.c',
'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',
],
c_args: [
@ -322,7 +322,7 @@ candidate_pair_gen_test = executable(
'src/image_catalog_persistent.c', 'src/feature_store.c',
'src/feature_extractor_opencv.cpp',
'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',
],
include_directories: include_directories('include'),
@ -334,7 +334,7 @@ candidate_pair_task_test = executable(
'test-candidate-pair-task',
sources: [
'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_kinds.c', 'src/matcher_task.c', 'src/matcher.cpp',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -364,7 +364,7 @@ matcher_task_test = executable(
'test-matcher-task',
sources: [
'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/matcher_task.c', 'src/matcher.cpp', 'src/match_file.c',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -390,7 +390,7 @@ feature_task_test = executable(
'test-feature-task',
sources: [
'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/matcher_task.c', 'src/matcher.cpp', 'src/match_file.c',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -424,7 +424,7 @@ precision_consolidation_test = executable(
'test-precision-consolidation',
sources: [
'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_kinds.c', 'src/matcher_task.c', 'src/matcher.cpp',
'src/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
@ -496,7 +496,7 @@ project_db_test = executable(
'test-project-db',
sources: [
'tests/test_project_db.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -508,11 +508,39 @@ project_db_test = executable(
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(
'test-match-result',
sources: [
'tests/test_match_result.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -528,7 +556,7 @@ geometric_verification_test = executable(
'test-geometric-verification',
sources: [
'tests/test_geometric_verification.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -546,7 +574,7 @@ project_test = executable(
'tests/test_project.c',
'src/app_state.c',
'src/project.c',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/task_checkpoint.c',
'src/task_kind_registry.c',
@ -679,7 +707,7 @@ matcher_test = executable(
'src/matcher.cpp',
'src/feature_store.c',
'src/feature_extractor_opencv.cpp',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -749,7 +777,7 @@ matcher_e2e_test = executable(
'src/matcher.cpp',
'src/feature_store.c',
'src/feature_extractor_opencv.cpp',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -771,7 +799,7 @@ executable(
'src/matcher.cpp',
'src/feature_store.c',
'src/feature_extractor_opencv.cpp',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -797,7 +825,7 @@ geometric_verifier_test = executable(
'src/match_file.c',
'src/feature_store.c',
'src/feature_extractor_opencv.cpp',
'src/project_db.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c',
'src/resource_governor.c',
'src/resource_snapshot.c',
@ -815,7 +843,7 @@ geometric_verifier_task_test = executable(
'test-geometric-verifier-task',
sources: [
'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/geometric_verifier_task.c', 'src/geometric_verifier.cpp',
'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_project.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/task.c',
'src/task_kind_registry.c',

View file

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

View file

@ -11,18 +11,20 @@
#include <lardon3d/project_db.h>
#define CHECK(condition) \
do { \
if (!(condition)) { \
fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \
} \
#define CHECK(condition) \
do { \
if (!(condition)) { \
fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \
} \
} 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_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) {
if (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 &&
sqlite3_column_int64(statement, 0) == expected &&
sqlite3_step(statement) == SQLITE_DONE;
return sqlite3_finalize(statement) == SQLITE_OK && sqlite3_close(connection) == SQLITE_OK &&
matches;
return sqlite3_finalize(statement) == SQLITE_OK &&
sqlite3_close(connection) == SQLITE_OK && matches;
}
static bool create_v9_database(const char *path) {
Lardon3DProjectDb *database = NULL;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
if (lardon3d_project_db_open(path, &database, error) != LARDON3D_PROJECT_DB_OK) return false;
if (lardon3d_project_db_open(path, &database, error) !=
LARDON3D_PROJECT_DB_OK)
return false;
lardon3d_project_db_close(database);
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[] =
"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_verification_results;"
"DROP TABLE matcher_tasks;"
"DROP TABLE match_results;"
"DROP TABLE track_observations;"
"DROP TABLE tracks;"
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;"
"UPDATE metadata SET value=9 WHERE key='schema_version';COMMIT;PRAGMA foreign_keys=ON;";
"DROP TABLE track_sets;DROP TABLE track_builder_tasks;"
"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;
return sqlite3_close(connection) == SQLITE_OK && ok;
}
static void asset_path_for_hash(const unsigned char hash[LARDON3D_PROJECT_DB_SHA256_SIZE],
char path[LARDON3D_PROJECT_DB_PATH_CAPACITY]) {
static void
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";
char hex[65];
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[64] = '\0';
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, "assets/images/%c%c/%s", hex[0], hex[1],
hex);
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY,
"assets/images/%c%c/%s", hex[0], hex[1], hex);
}
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[64] = '\0';
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY, "assets/features/%c%c/%s", hex[0], hex[1],
hex);
(void)snprintf(path, LARDON3D_PROJECT_DB_PATH_CAPACITY,
"assets/features/%c%c/%s", hex[0], hex[1], hex);
}
static bool run_test(void) {
char directory[] = "/tmp/lardon3d-match-result-XXXXXX";
CHECK(mkdtemp(directory));
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(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];
Lardon3DProjectDb *database = NULL;
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(database && lardon3d_project_db_schema_version(database) == 15);
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK);
CHECK(database && lardon3d_project_db_schema_version(database) == 16);
Lardon3DProjectDbScanSet 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);
Lardon3DProjectDbImageRegisterStatus identity_status;
Lardon3DProjectDbImage image_a;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_a, path_a, 1,
"a.jpg", "/src/a.jpg", 0, 1, &identity_status,
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_a,
path_a, 1, "a.jpg", "/src/a.jpg", 0,
1, &identity_status,
&image_a) == LARDON3D_PROJECT_DB_OK);
unsigned char hash_b[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xBB};
char path_b[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(hash_b, path_b);
Lardon3DProjectDbImage image_b;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_b, path_b, 1,
"b.jpg", "/src/b.jpg", 0, 2, &identity_status,
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_b,
path_b, 1, "b.jpg", "/src/b.jpg", 0,
2, &identity_status,
&image_b) == LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbCandidatePair pair;
CHECK(lardon3d_project_db_create_candidate_pair(database, image_a.image_id, image_b.image_id, 10,
&pair) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_create_candidate_pair(
database, image_a.image_id, image_b.image_id, 10, &pair) ==
LARDON3D_PROJECT_DB_OK);
CHECK(pair.candidate_pair_id > 0);
/* 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);
Lardon3DProjectDbFeatureSet fs_a;
CHECK(lardon3d_project_db_register_feature_set(
database, image_a.image_id, "orb", 1, feature_hash_a, hash_a, 100, 1, 32,
feature_hash_a, feature_path_a, 128, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 10,
database, image_a.image_id, "orb", 1, feature_hash_a, hash_a, 100,
1, 32, feature_hash_a, feature_path_a, 128,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 10,
&fs_a) == LARDON3D_PROJECT_DB_OK);
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);
Lardon3DProjectDbFeatureSet fs_b;
CHECK(lardon3d_project_db_register_feature_set(
database, image_b.image_id, "sift", 1, feature_hash_b, hash_b, 100, 2, 128,
feature_hash_b, feature_path_b, 256,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 20, &fs_b) ==
LARDON3D_PROJECT_DB_OK);
database, image_b.image_id, "sift", 1, feature_hash_b, hash_b, 100,
2, 128, feature_hash_b, feature_path_b, 256,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 20,
&fs_b) == LARDON3D_PROJECT_DB_OK);
CHECK(fs_b.feature_set_id > 0 && fs_b.image_id == image_b.image_id);
/* Create MATCHED result with inliers */
@ -156,14 +178,16 @@ static bool run_test(void) {
memset(match_hash, 0x5A, sizeof(match_hash));
const char *match_path = "assets/matches/5a/asset";
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100,
&result) == LARDON3D_PROJECT_DB_OK);
CHECK(result.match_result_id > 0 && result.candidate_pair_id == pair.candidate_pair_id &&
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
392, 100, &result) == LARDON3D_PROJECT_DB_OK);
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_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 &&
result.result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
result.match_count == 30 && result.created_at == 100);
@ -173,9 +197,9 @@ static bool run_test(void) {
unsigned char fp2[32];
memset(fp2, 0x02, 32);
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 2, fp2, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0,
NULL, NULL, 0, 200,
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 2, fp2,
LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, NULL, NULL, 0, 200,
&result) == LARDON3D_PROJECT_DB_OK);
CHECK(result.result_status == LARDON3D_MATCH_RESULT_STATUS_NO_MATCH &&
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 */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10,
match_hash, match_path, 152, 500,
&result) == LARDON3D_PROJECT_DB_CONSTRAINT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10, match_hash, match_path,
152, 500, &result) == LARDON3D_PROJECT_DB_CONSTRAINT);
/* Create additional Feature Sets for distinct-FS and inverted-ID tests */
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);
CHECK(lardon3d_project_db_register_feature_set(
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);
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];
memset(fs_b2_fp, 0xBB, 32);
CHECK(lardon3d_project_db_register_feature_set(
database, image_b.image_id, "sift", 2, fs_b2_fp, hash_b, 200, 2, 128,
feature_hash_b2, feature_path_b2, 512,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 40, &fs_b2) ==
LARDON3D_PROJECT_DB_OK);
database, image_b.image_id, "sift", 2, fs_b2_fp, hash_b, 200, 2,
128, feature_hash_b2, feature_path_b2, 512,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 40,
&fs_b2) == LARDON3D_PROJECT_DB_OK);
CHECK(fs_b2.feature_set_id > 0 && fs_b2.image_id == image_b.image_id);
/* Same CP + same matcher + same params + different FS → distinct */
@ -219,41 +244,46 @@ static bool run_test(void) {
memset(fp5, 0x05, 32);
Lardon3DProjectDbMatchResult distinct_fs_result;
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a2.feature_set_id, fs_b2.feature_set_id,
"orb_bf", 1, fp5, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 20,
match_hash, match_path, 272, 400,
&distinct_fs_result) == LARDON3D_PROJECT_DB_OK);
database, pair.candidate_pair_id, fs_a2.feature_set_id,
fs_b2.feature_set_id, "orb_bf", 1, fp5,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 20, match_hash, match_path,
272, 400, &distinct_fs_result) == LARDON3D_PROJECT_DB_OK);
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);
uint64_t distinct_id = distinct_fs_result.match_result_id;
CHECK(distinct_id != first_id);
/* 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};
char path_c[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(hash_c, path_c);
Lardon3DProjectDbImage image_c;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_c, path_c, 1,
"c.jpg", "/src/c.jpg", 0, 3, &identity_status,
&image_c) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_c,
path_c, 1, "c.jpg", "/src/c.jpg", 0,
3, &identity_status,
&image_c) == LARDON3D_PROJECT_DB_OK);
unsigned char hash_d[LARDON3D_PROJECT_DB_SHA256_SIZE] = {0xDD};
char path_d[LARDON3D_PROJECT_DB_PATH_CAPACITY];
asset_path_for_hash(hash_d, path_d);
Lardon3DProjectDbImage image_d;
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_d, path_d, 1,
"d.jpg", "/src/d.jpg", 0, 4, &identity_status,
&image_d) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_register_image(database, scanset.scanset_id, hash_d,
path_d, 1, "d.jpg", "/src/d.jpg", 0,
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};
char feature_path_d[LARDON3D_PROJECT_DB_PATH_CAPACITY];
feature_asset_path_for_hash(feature_hash_d, feature_path_d);
Lardon3DProjectDbFeatureSet fs_d;
CHECK(lardon3d_project_db_register_feature_set(
database, image_d.image_id, "orb", 1, feature_hash_d, hash_d, 100, 1, 32,
feature_hash_d, feature_path_d, 128, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 50,
database, image_d.image_id, "orb", 1, feature_hash_d, hash_d, 100,
1, 32, feature_hash_d, feature_path_d, 128,
LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 50,
&fs_d) == LARDON3D_PROJECT_DB_OK);
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);
Lardon3DProjectDbFeatureSet fs_c;
CHECK(lardon3d_project_db_register_feature_set(
database, image_c.image_id, "sift", 1, feature_hash_c, hash_c, 100, 2, 128,
feature_hash_c, feature_path_c, 256,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 60, &fs_c) ==
LARDON3D_PROJECT_DB_OK);
database, image_c.image_id, "sift", 1, feature_hash_c, hash_c, 100,
2, 128, feature_hash_c, feature_path_c, 256,
LARDON3D_DB_FEATURE_ASSET_PUBLISHED_NOT_DURABLE, 0, 60,
&fs_c) == LARDON3D_PROJECT_DB_OK);
/* 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);
/* CP (image_c, image_d): image_c should come first since registered first */
Lardon3DProjectDbCandidatePair pair_cd;
CHECK(lardon3d_project_db_create_candidate_pair(database, image_c.image_id, image_d.image_id, 20,
&pair_cd) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_create_candidate_pair(
database, image_c.image_id, image_d.image_id, 20, &pair_cd) ==
LARDON3D_PROJECT_DB_OK);
unsigned char fp6[32];
memset(fp6, 0x06, 32);
Lardon3DProjectDbMatchResult inverted_fs_result;
CHECK(lardon3d_project_db_create_match_result(
database, pair_cd.candidate_pair_id, fs_c.feature_set_id, fs_d.feature_set_id,
"orb_bf", 1, fp6, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 15,
match_hash, match_path, 212, 500,
&inverted_fs_result) == LARDON3D_PROJECT_DB_OK);
database, pair_cd.candidate_pair_id, fs_c.feature_set_id,
fs_d.feature_set_id, "orb_bf", 1, fp6,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 15, match_hash, match_path,
212, 500, &inverted_fs_result) == LARDON3D_PROJECT_DB_OK);
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);
CHECK(fs_c.feature_set_id > fs_d.feature_set_id);
@ -289,26 +320,27 @@ static bool run_test(void) {
unsigned char fp7[32];
memset(fp7, 0x07, 32);
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_b.feature_set_id, fs_a.feature_set_id,
"orb_bf", 1, fp7, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5,
match_hash, match_path, 92, 600,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_b.feature_set_id,
fs_a.feature_set_id, "orb_bf", 1, fp7,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5, match_hash, match_path, 92,
600, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* FeatureSet B belonging to image A (not image B) → rejected */
unsigned char fp8[32];
memset(fp8, 0x08, 32);
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_a2.feature_set_id,
"orb_bf", 1, fp8, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5,
match_hash, match_path, 92, 700,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_a2.feature_set_id, "orb_bf", 1, fp8,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 5, match_hash, match_path, 92,
700, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Load by ID */
Lardon3DProjectDbMatchResult loaded;
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
LARDON3D_PROJECT_DB_OK);
CHECK(loaded.match_result_id == first_id && loaded.result_status ==
LARDON3D_MATCH_RESULT_STATUS_MATCHED && loaded.match_count == 30);
CHECK(loaded.match_result_id == first_id &&
loaded.result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
loaded.match_count == 30);
/* Load not found */
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 */
Lardon3DProjectDbMatchResult found;
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id,
fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_find_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_OK);
CHECK(found.match_result_id == first_id && found.match_count == 30);
/* Find with wrong feature_set_id → NOT_FOUND */
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id,
fs_a2.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_NOT_FOUND);
CHECK(lardon3d_project_db_find_match_result(
database, pair.candidate_pair_id, fs_a2.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_NOT_FOUND);
/* Find not found */
unsigned char unknown_fp[32];
memset(unknown_fp, 0xFF, 32);
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id,
fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, unknown_fp,
&found) == LARDON3D_PROJECT_DB_NOT_FOUND);
CHECK(lardon3d_project_db_find_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, unknown_fp,
&found) == LARDON3D_PROJECT_DB_NOT_FOUND);
/* Find with wrong candidate_pair */
CHECK(lardon3d_project_db_find_match_result(database, 999999,
fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_NOT_FOUND);
CHECK(lardon3d_project_db_find_match_result(
database, 999999, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, &found) == LARDON3D_PROJECT_DB_NOT_FOUND);
/* Pagination */
Lardon3DProjectDbMatchResult page[LARDON3D_PROJECT_DB_MATCH_RESULT_PAGE_MAX];
size_t page_count = 0;
CHECK(lardon3d_project_db_list_match_results(database, 0, page, 4, &page_count) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_list_match_results(
database, 0, page, 4, &page_count) == LARDON3D_PROJECT_DB_OK);
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 */
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);
CHECK(page_count == 2 && page[0].match_result_id == second_id &&
page[1].match_result_id == distinct_id);
/* End of list */
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);
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);
/* Invalid arguments: NULL database */
CHECK(lardon3d_project_db_create_match_result(NULL, pair.candidate_pair_id,
fs_a.feature_set_id, fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_match_result(
NULL, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: 0 ids */
CHECK(lardon3d_project_db_create_match_result(
database, 0, fs_a.feature_set_id, fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, 0, fs_a.feature_set_id, fs_b.feature_set_id, "orb_bf", 1,
fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash,
match_path, 392, 100,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, 0, fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, 0, fs_b.feature_set_id, "orb_bf",
1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash,
match_path, 392, 100,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: NULL output */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100, NULL) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
392, 100, NULL) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: empty matcher_kind */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: 0 matcher_version */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 0, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 0, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: NULL fingerprint */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, NULL, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, NULL,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: bad status */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, 2, 30,
match_hash, match_path, 392, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1, 2, 30, match_hash,
match_path, 392, 100,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid result/asset combinations */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10,
NULL, NULL, 0, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 10, NULL, NULL, 0, 100,
&result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"bad-no-match", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0,
match_hash, match_path, 32, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "bad-no-match", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, match_hash, match_path,
32, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"too-many", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 8193,
match_hash, match_path, 98348, 100, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "too-many", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 8193, match_hash, match_path,
98348, 100, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Invalid arguments: negative created_at */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30,
match_hash, match_path, 392, -1, &result) ==
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_MATCHED, 30, match_hash, match_path,
392, -1, &result) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
/* Status boundary values */
CHECK(lardon3d_project_db_create_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id, fs_b.feature_set_id,
"boundary", 1, fp1, LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0,
NULL, NULL, 0, 500, &result) ==
LARDON3D_PROJECT_DB_OK);
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "boundary", 1, fp1,
LARDON3D_MATCH_RESULT_STATUS_NO_MATCH, 0, NULL, NULL, 0, 500,
&result) == LARDON3D_PROJECT_DB_OK);
/* Close/reopen persistence */
lardon3d_project_db_close(database);
database = NULL;
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 15);
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 16);
/* Verify persistence: load previously created results */
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
LARDON3D_PROJECT_DB_OK);
CHECK(loaded.match_result_id == first_id && loaded.result_status ==
LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
CHECK(loaded.match_result_id == first_id &&
loaded.result_status == LARDON3D_MATCH_RESULT_STATUS_MATCHED &&
strcmp(loaded.matcher_kind, "orb_bf") == 0 && loaded.match_count == 30);
/* Verify persistence: find by key */
CHECK(lardon3d_project_db_find_match_result(database, pair.candidate_pair_id,
fs_a.feature_set_id, fs_b.feature_set_id,
"orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_find_match_result(
database, pair.candidate_pair_id, fs_a.feature_set_id,
fs_b.feature_set_id, "orb_bf", 1, fp1,
&found) == LARDON3D_PROJECT_DB_OK);
CHECK(found.match_result_id == first_id);
/* Verify persistence of distinct result */
Lardon3DProjectDbMatchResult distinct_loaded;
CHECK(lardon3d_project_db_load_match_result(database, distinct_id, &distinct_loaded) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_match_result(
database, distinct_id, &distinct_loaded) == LARDON3D_PROJECT_DB_OK);
CHECK(distinct_loaded.match_result_id == distinct_id &&
distinct_loaded.feature_set_id_a == fs_a2.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 */
Lardon3DProjectDbMatchResult inverted_loaded;
CHECK(lardon3d_project_db_load_match_result(database, inverted_fs_result.match_result_id,
&inverted_loaded) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_load_match_result(
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 &&
inverted_loaded.feature_set_id_b == fs_d.feature_set_id);
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 */
CHECK(create_v9_database(v9_path));
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
CHECK(lardon3d_project_db_open(v9_path, &database, error) == LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 15);
CHECK(query_integer(
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
CHECK(lardon3d_project_db_open(v9_path, &database, error) ==
LARDON3D_PROJECT_DB_OK);
CHECK(lardon3d_project_db_schema_version(database) == 16);
lardon3d_project_db_close(database);
database = NULL;
CHECK(query_integer(v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 15));
CHECK(query_integer(v9_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'", 1));
CHECK(query_integer(
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
CHECK(
query_integer(v9_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'",
1));
/* Forced migration failure v10 */
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) ==
LARDON3D_PROJECT_DB_IO_ERROR);
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,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'", 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) == 15);
"SELECT value FROM metadata WHERE key='schema_version'",
9));
CHECK(
query_integer(failed_v10_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'",
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);
database = NULL;
@ -530,6 +579,4 @@ static bool run_test(void) {
return true;
}
int main(void) {
return run_test() ? EXIT_SUCCESS : EXIT_FAILURE;
}
int main(void) { 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 *connection = NULL;
sqlite3_stmt *statement = NULL;
bool success = sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY,
NULL) == SQLITE_OK &&
sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) ==
SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW &&
sqlite3_column_int64(statement, 0) == expected;
bool success =
sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) ==
SQLITE_OK &&
sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) == SQLITE_OK &&
sqlite3_step(statement) == SQLITE_ROW &&
sqlite3_column_int64(statement, 0) == expected;
if (statement) {
(void)sqlite3_finalize(statement);
}
@ -89,12 +89,20 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
}
static const char sql[] =
"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_verification_results;"
"DROP TABLE matcher_tasks;"
"DROP TABLE track_observations;"
"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';"
"COMMIT;PRAGMA foreign_keys=ON;";
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,
"SELECT value FROM metadata WHERE key='schema_version'",
10));
CHECK(query_integer(database_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'",
1));
CHECK(query_integer(database_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'",
0));
CHECK(
query_integer(database_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='match_results'",
1));
CHECK(
query_integer(database_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'",
0));
CHECK(reopen_runtime(&fixture));
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 15);
CHECK(query_integer(database_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'",
1));
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 16);
CHECK(
query_integer(database_path,
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
"name='matcher_tasks'",
1));
const Lardon3DTaskKindRegistry *registry =
lardon3d_task_kind_registry_production();
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;
}