feat(sparse-sfm): implement and freeze Gate F
This commit is contained in:
parent
a67d903a8c
commit
2e0104162c
26 changed files with 2275 additions and 78 deletions
|
|
@ -29,9 +29,11 @@ persistante, enrichissable et versionnable.
|
||||||
- **Image View** : vues triées et filtrées pour la TUI
|
- **Image View** : vues triées et filtrées pour la TUI
|
||||||
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences
|
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences
|
||||||
- **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre
|
- **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre
|
||||||
- **Project Database v16** : résultats géométriques, Track Model v1 et persistance Sparse SfM
|
- **Project Database v17** : résultats v16 et payload durable typé Sparse SfM Gate F
|
||||||
- **Sparse SfM Gates C/D/E** : géométrie calibrée, noyau incrémental et Bundle
|
- **Sparse SfM Gates C/D/E** : géométrie calibrée, noyau incrémental et Bundle
|
||||||
Adjustment final par composante, tous PASS / FROZEN
|
Adjustment final par composante, tous PASS / FROZEN
|
||||||
|
- **Sparse SfM Gate F** : orchestration durable, runtime gouverné et publication
|
||||||
|
atomique, PASS / FROZEN
|
||||||
- **Geometric Verification Model v1** : identité, masque d'inliers et modèle 3×3 persistants
|
- **Geometric Verification Model v1** : identité, masque d'inliers et modèle 3×3 persistants
|
||||||
- **Geometric Verifier v1** : Fundamental USAC/MAGSAC, reprise et lots resource-aware
|
- **Geometric Verifier v1** : Fundamental USAC/MAGSAC, reprise et lots resource-aware
|
||||||
- **Task Kind Registry** : identité métier durable et reconstruction runtime explicite
|
- **Task Kind Registry** : identité métier durable et reconstruction runtime explicite
|
||||||
|
|
@ -169,8 +171,8 @@ de petits lots, le Resource Governor interactif et un hot path Vulkan ORB exact
|
||||||
fallback CPU. La feasibility Vulkan SIFT/RootSIFT a été rejetée ; ces deux matchers
|
fallback CPU. La feasibility Vulkan SIFT/RootSIFT a été rejetée ; ces deux matchers
|
||||||
restent sur OpenCV L2. Track Model/Builder, les primitives géométriques Gate C,
|
restent sur OpenCV L2. Track Model/Builder, les primitives géométriques Gate C,
|
||||||
le noyau Sparse SfM incrémental Gate D et le Bundle Adjustment final Gate E sont
|
le noyau Sparse SfM incrémental Gate D et le Bundle Adjustment final Gate E sont
|
||||||
implémentés et validés. L'orchestration Sparse SfM Gate F, l'intégration
|
implémentés et validés. L'orchestration Sparse SfM Gate F est PASS / FROZEN ;
|
||||||
Governor Gate G, le DAG, le viewer et les étapes denses restent des tickets
|
l'intégration Governor Gate G, le DAG, le viewer et les étapes denses restent des tickets
|
||||||
séparés planifiés. Le Resource Governor ne
|
séparés planifiés. Le Resource Governor ne
|
||||||
constitue pas un Resource System générique : voir la décision d’architecture.
|
constitue pas un Resource System générique : voir la décision d’architecture.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ les Feature Sets persistés.
|
||||||
PASS**, the Sparse SfM v16 persistence model is **FROZEN** after Gate B;
|
PASS**, the Sparse SfM v16 persistence model is **FROZEN** after Gate B;
|
||||||
Gate C geometry and the synchronous in-memory Gate D incremental core are
|
Gate C geometry and the synchronous in-memory Gate D incremental core are
|
||||||
**IMPLEMENTED / PASS**. Final per-component Gate E BA is **PASS / FROZEN**;
|
**IMPLEMENTED / PASS**. Final per-component Gate E BA is **PASS / FROZEN**;
|
||||||
Gate F project/task orchestration remains **PLANNED**.
|
Gate F project/task orchestration is **PASS / FROZEN**. Gate G remains planned.
|
||||||
|
|
||||||
## Extension v2 multi-descriptor
|
## Extension v2 multi-descriptor
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,11 @@ ni persistance, ni Task Runtime, ni Resource Governor.
|
||||||
|
|
||||||
**Statut :** GATES C/D/E — PASS / FROZEN
|
**Statut :** GATES C/D/E — PASS / FROZEN
|
||||||
|
|
||||||
|
Gate F relie ces noyaux au Task Runtime durable, à la Queue/Governor/Reservation
|
||||||
|
et à la publication Project DB v17 atomique et idempotente.
|
||||||
|
|
||||||
|
**Statut :** GATE F — PASS / FROZEN. Gate G reste planifiée.
|
||||||
|
|
||||||
## Résultats et publication live
|
## Résultats et publication live
|
||||||
|
|
||||||
Les traitements fonctionnent par séquences adaptatives : lire un lot borné,
|
Les traitements fonctionnent par séquences adaptatives : lire un lot borné,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,45 @@
|
||||||
# Base de données projet Lardon3D
|
# Base de données projet Lardon3D
|
||||||
|
|
||||||
> Version courante : **v16**. La migration transactionnelle v15→v16 ajoute
|
## Gate F — décision Project DB v17
|
||||||
|
|
||||||
|
**PASS / FROZEN.** La migration historique v15→v16 et le
|
||||||
|
modèle de reconstruction Sparse SfM v16 restent inchangés. Gate F fait avancer
|
||||||
|
la tête de schéma à v17 par une migration strictement additive contenant une
|
||||||
|
seule table métier de tâche : `sparse_sfm_tasks`.
|
||||||
|
|
||||||
|
Cette table suit le modèle des autres Task Kinds durables : une ligne par
|
||||||
|
`task_id`, clé primaire et clé étrangère vers `tasks(task_id)` avec suppression
|
||||||
|
en cascade. Elle conserve les références immuables `track_set_id` et
|
||||||
|
`calibration_scope_id`, `sfm_kind`, `sfm_version`, puis les 27 valeurs effectives
|
||||||
|
de `Lardon3DSparseIncrementalParameters`, y compris les sous-structures
|
||||||
|
relative-pose, PnP et refinement. Les entiers suivent leurs largeurs C
|
||||||
|
canoniques ; les flottants finis sont stockés en SQLite `REAL` et doivent
|
||||||
|
retrouver exactement leurs bits binary64 après fermeture/réouverture.
|
||||||
|
|
||||||
|
Le Task Kind et sa version restent dans `tasks` et versionnent l'interprétation
|
||||||
|
du payload. Le fingerprint F0 est dérivé au rechargement et n'est pas dupliqué.
|
||||||
|
Le checkpoint générique v1 n'est pas modifié. L'écriture du résumé générique et
|
||||||
|
du payload typé est une transaction unique ; une ligne absente, incompatible
|
||||||
|
ou invalide interdit la reconstruction runtime sans appliquer de valeurs par
|
||||||
|
défaut.
|
||||||
|
|
||||||
|
Les quatre champs `uint64_t` de domaine complet — limites observations/Tracks
|
||||||
|
et seeds déterministes relative-pose/PnP — sont chacun un BLOB de huit octets
|
||||||
|
little-endian avec `CHECK(length(...)=8)`. L'API exige la classe BLOB et la
|
||||||
|
longueur exacte au rechargement. Les métriques globales persistées restent les
|
||||||
|
diagnostics Gate F calculés sur toutes les observations retenues du résultat
|
||||||
|
Gate E final ; elles ne participent pas à l'identité.
|
||||||
|
|
||||||
|
La publication Gate F est la projection durable du résultat scientifique
|
||||||
|
complet : une composante est persistée si et seulement si ses nombres d'images
|
||||||
|
enregistrées et de landmarks sont tous deux strictement positifs. Une
|
||||||
|
composante dont le BA est rejeté mais dont la géométrie Gate D reste valide est
|
||||||
|
persistée exactement ; seule une composante de graphe non reconstruite est
|
||||||
|
omise. Les comptes et métriques globaux décrivent exclusivement la géométrie
|
||||||
|
effectivement persistée. Aucun placeholder ni table diagnostique n'est ajouté.
|
||||||
|
|
||||||
|
> Version courante : **v17**. La migration transactionnelle v16→v17 ajoute le
|
||||||
|
> payload durable typé `sparse_sfm_tasks`. La migration transactionnelle v15→v16 ajoute
|
||||||
> le modèle persistant Sparse SfM (calibrations, scopes et reconstructions).
|
> le modèle persistant Sparse SfM (calibrations, scopes et reconstructions).
|
||||||
> La migration transactionnelle v14→v15 ajoute
|
> La migration transactionnelle v14→v15 ajoute
|
||||||
> `track_builder_tasks` pour le payload durable explicite du Task Builder. La
|
> `track_builder_tasks` pour le payload durable explicite du Task Builder. La
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ implémenté dans Project DB v15 (`track_sets`, `tracks`, `track_observations` e
|
||||||
le payload de tâche). Les primitives de géométrie calibrée Gate C sont
|
le payload de tâche). Les primitives de géométrie calibrée Gate C sont
|
||||||
implémentées et le noyau Sparse SfM incrémental Gate D est IMPLEMENTED / PASS ;
|
implémentées et le noyau Sparse SfM incrémental Gate D est IMPLEMENTED / PASS ;
|
||||||
le Bundle Adjustment final Gate E est PASS / FROZEN ; l'orchestration projet
|
le Bundle Adjustment final Gate E est PASS / FROZEN ; l'orchestration projet
|
||||||
et tâche Gate F reste PLANNED.
|
et tâche Gate F est PASS / FROZEN.
|
||||||
Le modèle de persistance Sparse SfM v16 est gelé après Gate B.
|
Le modèle de persistance Sparse SfM v16 est gelé après Gate B.
|
||||||
|
|
||||||
**Sparse SfM Gate A : PASS.** Le contrat géométrique, la stratégie
|
**Sparse SfM Gate A : PASS.** Le contrat géométrique, la stratégie
|
||||||
|
|
@ -136,8 +136,9 @@ les limites BA et l'enveloppe matérielle sont documentés dans
|
||||||
`architecture/sparse_sfm.md`. Ses primitives pures calibrées Gate C sont
|
`architecture/sparse_sfm.md`. Ses primitives pures calibrées Gate C sont
|
||||||
**IMPLEMENTED / PASS** et son noyau incrémental synchrone en mémoire Gate D est
|
**IMPLEMENTED / PASS** et son noyau incrémental synchrone en mémoire Gate D est
|
||||||
**IMPLEMENTED / PASS**. Le Bundle Adjustment final par composante Gate E est
|
**IMPLEMENTED / PASS**. Le Bundle Adjustment final par composante Gate E est
|
||||||
**PASS / FROZEN**. L'orchestration Gate F reste **PLANNED**, tandis que la
|
**PASS / FROZEN**. L'orchestration Gate F est **PASS / FROZEN**. Le modèle de
|
||||||
persistance v16 et ses lecteurs bornés restent ceux de B2.
|
reconstruction v16 et ses lecteurs bornés restent ceux de B2 ; le payload de
|
||||||
|
tâche Gate F est l'ajout v17.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -258,8 +259,9 @@ Import, Image Catalog, Feature Extraction, Feature Store, Visual Index,
|
||||||
Candidate Pair, Matching v1, Geometric Verification, Track Model/Builder v1
|
Candidate Pair, Matching v1, Geometric Verification, Track Model/Builder v1
|
||||||
and Sparse SfM Gate C geometry are **IMPLEMENTED**. The synchronous in-memory
|
and Sparse SfM Gate C geometry are **IMPLEMENTED**. The synchronous in-memory
|
||||||
incremental Sparse SfM Gate D core is **IMPLEMENTED / PASS**, and final
|
incremental Sparse SfM Gate D core is **IMPLEMENTED / PASS**, and final
|
||||||
per-component Gate E BA is **PASS / FROZEN**. Gate F orchestration,
|
per-component Gate E BA is **PASS / FROZEN**. Gate F orchestration is
|
||||||
Gate G Governor integration, MVS, mesh, texturing and viewer remain **PLANNED**.
|
**PASS / FROZEN**. Gate G Governor integration, MVS, mesh,
|
||||||
|
texturing and viewer remain **PLANNED**.
|
||||||
|
|
||||||
Ce document décrit la vision architecturale cible du pipeline de
|
Ce document décrit la vision architecturale cible du pipeline de
|
||||||
reconstruction. Les modules listés ici ne sont pas tous implémentés.
|
reconstruction. Les modules listés ici ne sont pas tous implémentés.
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@
|
||||||
|
|
||||||
**ACCEPTED** — architecture decision for the post-Gate C documentation freeze.
|
**ACCEPTED** — architecture decision for the post-Gate C documentation freeze.
|
||||||
|
|
||||||
Current gates: Gate A **PASS**, Gate B **PASS / FROZEN**, Gate C **PASS**.
|
Current Sparse SfM gates A through F are **PASS / FROZEN**. Gate G remains
|
||||||
Project Database: **v16**.
|
open/planned.
|
||||||
|
Project Database: current schema **v17**; historical v16 remains frozen.
|
||||||
|
|
||||||
This record is normative for the current architecture. It does not introduce
|
This record is normative for the current architecture. It does not introduce
|
||||||
an implementation, a public API, a persistence format or a roadmap commitment.
|
an implementation, a public API, a persistence format or a roadmap commitment.
|
||||||
|
|
@ -94,15 +95,47 @@ Tasks create estimates, request reservations, execute bounded work and release
|
||||||
reservations. The Governor owns only policy, budgets, pressure, lots and
|
reservations. The Governor owns only policy, budgets, pressure, lots and
|
||||||
reservations.
|
reservations.
|
||||||
|
|
||||||
## Project DB v16 boundary
|
### Task demand declaration versus admission policy
|
||||||
|
|
||||||
Project DB v16 remains unchanged and frozen. It already stores the current
|
**FROZEN.** A task producer owns the immutable declaration of the workload it
|
||||||
logical IDs, content hashes, sizes, states, relations and artifact paths needed
|
submits through the existing `Lardon3DResourceEstimate` contract. Constructing
|
||||||
by implemented stores. No resource table, handle table, cache table,
|
that estimate from immutable payload, input shape and known implementation
|
||||||
dependency table, schema field or migration is required by this decision.
|
characteristics describes what the task expects to require; it is not a
|
||||||
|
Governor policy decision.
|
||||||
|
|
||||||
No Project DB v17, bundle redesign, Track Model change, Track Builder change or
|
The existing Task Runtime, queue and Governor remain responsible for deciding
|
||||||
persistence API redesign is part of this scope.
|
whether and when the declared work is admitted and for creating the mandatory
|
||||||
|
reservation. Admission thresholds, current machine state, pressure, telemetry,
|
||||||
|
adaptive tuning, swap or scratch policy and future estimate calibration remain
|
||||||
|
outside the task producer. A producer never changes scientific inputs or
|
||||||
|
identity in response to resources.
|
||||||
|
|
||||||
|
Gate F may therefore construct the immutable Sparse SfM task estimate required
|
||||||
|
by `lardon3d_task_create_typed()` and submit the task through the normal queue.
|
||||||
|
It does not inspect resource snapshots, decide admission, create reservations,
|
||||||
|
change Governor or queue policy, or bypass the reservation invariant. The
|
||||||
|
estimate is operational metadata and is excluded from the Sparse SfM parameter
|
||||||
|
fingerprint, candidate identity and scientific determinism. Future Gate G owns
|
||||||
|
resource-management policy and operational refinement of that estimate.
|
||||||
|
|
||||||
|
Gate F v1 freezes its declarative Sparse SfM RAM request as the checked sum
|
||||||
|
`128 MiB + I*64 KiB + T*2048 + O*512`, rounded upward to one MiB, where `I`,
|
||||||
|
`T` and `O` are immutable participating-image, Track and observation counts.
|
||||||
|
The complete amount is fixed RAM for one atomic batch; per-item RAM and all GPU
|
||||||
|
fields are zero, batch bounds are one, and the task requests one CPU thread and
|
||||||
|
one IO slot in the CPU class. These coefficients are conservative operational
|
||||||
|
policy inputs, not measured dynamically or included in scientific identity.
|
||||||
|
|
||||||
|
## Project DB boundary
|
||||||
|
|
||||||
|
The historical Project DB v16 migration and Sparse SfM reconstruction model
|
||||||
|
remain unchanged and frozen. Gate F is authorized to advance the current schema
|
||||||
|
head to v17 only with the dedicated `sparse_sfm_tasks` typed-payload table; that
|
||||||
|
task persistence does not add a resource table, handle table, cache table,
|
||||||
|
dependency table or resource-policy field.
|
||||||
|
|
||||||
|
No further schema expansion, bundle redesign, Track Model change, Track Builder
|
||||||
|
change or persistence API redesign is part of this resource-boundary scope.
|
||||||
|
|
||||||
## Options considered
|
## Options considered
|
||||||
|
|
||||||
|
|
@ -164,7 +197,7 @@ resolver or Governor asset integration as part of the current architecture.
|
||||||
- Generic dependency graph or cycle management.
|
- Generic dependency graph or cycle management.
|
||||||
- Streaming or asynchronous resource loading.
|
- Streaming or asynchronous resource loading.
|
||||||
- CPU/GPU dual-residency manager.
|
- CPU/GPU dual-residency manager.
|
||||||
- Project DB v17 or schema migration.
|
- Project DB schema beyond the additive Gate F v17 typed-task migration.
|
||||||
- Bundle redesign.
|
- Bundle redesign.
|
||||||
- Governor integration with persistent asset identity.
|
- Governor integration with persistent asset identity.
|
||||||
- BA, new orchestration, Task Runtime redesign or renderer redesign.
|
- BA, new orchestration, Task Runtime redesign or renderer redesign.
|
||||||
|
|
@ -178,8 +211,9 @@ contracts remain authoritative in their own domains.
|
||||||
|
|
||||||
## Persistence impact
|
## Persistence impact
|
||||||
|
|
||||||
None. Project DB remains v16. Existing IDs, hashes, metadata and relative paths
|
The historical Project DB v16 migration remains unchanged. Gate F adds only the
|
||||||
are not renamed, generalized or duplicated.
|
v17 typed-task payload already described above. Existing IDs, hashes, metadata
|
||||||
|
and relative paths are not renamed, generalized or duplicated.
|
||||||
|
|
||||||
## Memory / resource impact
|
## Memory / resource impact
|
||||||
|
|
||||||
|
|
@ -239,7 +273,8 @@ These are trigger conditions, not current requirements or roadmap commitments.
|
||||||
- No Track Model or Track Builder contract is changed.
|
- No Track Model or Track Builder contract is changed.
|
||||||
- No bundle or migration is introduced.
|
- No bundle or migration is introduced.
|
||||||
- Governor ownership remains limited to execution budgets.
|
- Governor ownership remains limited to execution budgets.
|
||||||
- Project DB remains v16.
|
- At the Gate B freeze, Project DB remained v16; the later additive Gate F v17
|
||||||
|
migration does not alter that historical proof.
|
||||||
|
|
||||||
Therefore Gate B remains **PASS / FROZEN**.
|
Therefore Gate B remains **PASS / FROZEN**.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -198,7 +198,7 @@ as a whole, so Track identity and observation ownership remain simple.
|
||||||
Bundle Adjustment implementation, E01--E35 matrix, normal suite, targeted
|
Bundle Adjustment implementation, E01--E35 matrix, normal suite, targeted
|
||||||
ASan/UBSan with LeakSanitizer, full sequential ASan/UBSan suite and at least
|
ASan/UBSan with LeakSanitizer, full sequential ASan/UBSan suite and at least
|
||||||
20 fresh-process E27 comparisons are validated. Gate F project orchestration
|
20 fresh-process E27 comparisons are validated. Gate F project orchestration
|
||||||
and Gate G resource integration remain later gates.
|
is now **PASS / FROZEN**; Gate G resource integration remains a later gate.
|
||||||
|
|
||||||
**DECISION: Gate E v1 is a synchronous, independent final per-component Bundle
|
**DECISION: Gate E v1 is a synchronous, independent final per-component Bundle
|
||||||
Adjustment applied as post-processing to a copy of the immutable final Gate D
|
Adjustment applied as post-processing to a copy of the immutable final Gate D
|
||||||
|
|
@ -779,6 +779,185 @@ solver until measured. The v1 target is deterministic ordering and numerical
|
||||||
reproducibility within documented tolerances; single-threaded reductions are
|
reproducibility within documented tolerances; single-threaded reductions are
|
||||||
the initial reference.
|
the initial reference.
|
||||||
|
|
||||||
|
### Gate F publication policy
|
||||||
|
|
||||||
|
**FROZEN.** Gate F keeps the Gate E execution domain separate from the Gate E
|
||||||
|
scientific result domain. `EXECUTION_OK` is the necessary and sufficient Gate E
|
||||||
|
condition for publication eligibility. Under that execution status,
|
||||||
|
`COMPLETE`, `PARTIAL` and `FAILED` are all valid complete in-memory Gate E
|
||||||
|
results and are published atomically. A successful Project DB publication or
|
||||||
|
exact-identity reuse makes the Task Runtime execution successful for all three
|
||||||
|
scientific statuses.
|
||||||
|
|
||||||
|
| Gate E execution | Scientific status | Publish | Runtime after DB success |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `EXECUTION_OK` | `COMPLETE` | exact Gate E result | success |
|
||||||
|
| `EXECUTION_OK` | `PARTIAL` | exact Gate E result | success |
|
||||||
|
| `EXECUTION_OK` | `FAILED` | exact Gate E result | success |
|
||||||
|
| `EXECUTION_INVALID_ARGUMENT` | none | no publication | failure |
|
||||||
|
| `EXECUTION_OUT_OF_MEMORY` | none | no publication | failure |
|
||||||
|
| `EXECUTION_INTERNAL_ERROR` | none | no publication | failure |
|
||||||
|
|
||||||
|
Scientific `PARTIAL` describes a complete result in which accepted components
|
||||||
|
contain validated optimized values and rejected components preserve their exact
|
||||||
|
Gate D values. It never permits partial database visibility. Scientific
|
||||||
|
`FAILED` means that no eligible component accepted Bundle Adjustment; under
|
||||||
|
`EXECUTION_OK` it remains a valid complete Gate E result whose components
|
||||||
|
preserve the Gate D values selected by Gate E.
|
||||||
|
|
||||||
|
Gate F never publishes Gate D as a fallback after a Gate E execution error. It
|
||||||
|
does not rerun Gate D or Gate E, retry automatically, reinterpret scientific
|
||||||
|
status as a Task Runtime state, or require scientific `COMPLETE` for runtime
|
||||||
|
success. A Project DB publication failure rolls back and fails the runtime
|
||||||
|
execution. The scientific status is output diagnostic metadata, not part of
|
||||||
|
the candidate identity or parameter fingerprint. Gate F uses an existing
|
||||||
|
status carrier when one exists; the absence of a dedicated Project DB v16
|
||||||
|
column does not require a migration or authorize reuse of an unrelated column.
|
||||||
|
|
||||||
|
### Gate F resource-demand boundary
|
||||||
|
|
||||||
|
**FROZEN.** Gate F materializes the immutable `Lardon3DResourceEstimate` needed
|
||||||
|
to describe its task to the existing Task Runtime. The estimate is a pure
|
||||||
|
function of durable task input shape and known implementation characteristics;
|
||||||
|
it never depends on current RAM, swap, PSI, load, queue depth, task attempt or
|
||||||
|
Governor state. Gate F submits through the existing queue and does not perform
|
||||||
|
admission or reservation itself.
|
||||||
|
|
||||||
|
The existing Governor owns admission and reservation policy. Future Gate G owns
|
||||||
|
telemetry, adaptive tuning, pressure and swap/scratch policy, scheduling policy
|
||||||
|
and estimate refinement. The estimate never enters the 372-byte parameter
|
||||||
|
record, candidate identity, `sfm_version` or scientific decisions, and cannot
|
||||||
|
change Gate D or Gate E parameters. This clarification preserves
|
||||||
|
`NO_NEW_SUBSYSTEM` and the mandatory reservation invariant.
|
||||||
|
|
||||||
|
### Gate F durable task payload
|
||||||
|
|
||||||
|
**FROZEN.** Gate F advances the current Project Database schema head from v16
|
||||||
|
to v17 with one strictly additive `sparse_sfm_tasks` table. The historical v16
|
||||||
|
migration and its immutable reconstruction model remain unchanged. The new
|
||||||
|
table follows the existing one-to-one typed-task pattern: its primary key is a
|
||||||
|
foreign key to `tasks(task_id)` with cascade cleanup, and creation records the
|
||||||
|
generic task snapshot and typed payload in one transaction.
|
||||||
|
|
||||||
|
The payload stores the immutable Track Set reference, calibration-scope
|
||||||
|
reference, Sparse SfM kind and version, and every one of the 27 effective
|
||||||
|
scalars in `Lardon3DSparseIncrementalParameters`, using their existing fixed
|
||||||
|
integer widths and exact finite SQLite binary64 values. The Task Kind version
|
||||||
|
selects the payload interpretation. Reload never reapplies defaults and rejects
|
||||||
|
a missing row, incompatible kind/version, invalid identity or invalid
|
||||||
|
parameter. The generic checkpoint codec remains v1 and unchanged.
|
||||||
|
|
||||||
|
The parameter fingerprint is not stored in `sparse_sfm_tasks`. Reconstruction
|
||||||
|
loads and validates the effective fields, rebuilds the unchanged 372-byte F0
|
||||||
|
record and recomputes SHA-256. Calibration values, Track observations,
|
||||||
|
ResourceEstimate and runtime metadata are not duplicated in the typed payload.
|
||||||
|
This is an additive Project DB/typed-Task extension, not a generic payload or
|
||||||
|
persistence subsystem.
|
||||||
|
|
||||||
|
### Gate F closure decisions
|
||||||
|
|
||||||
|
**FROZEN.** The v1 declarative estimate uses the immutable pre-admission counts
|
||||||
|
`I` (distinct participating images), `T` (Tracks) and `O` (Track observations):
|
||||||
|
|
||||||
|
```text
|
||||||
|
raw = 134217728 + I*65536 + T*2048 + O*512
|
||||||
|
memory_fixed_bytes = raw rounded upward to a whole MiB
|
||||||
|
```
|
||||||
|
|
||||||
|
Every operation is checked `uint64_t` arithmetic; overflow rejects task
|
||||||
|
creation before persistence. The remaining estimate is RAM-per-item 0, all GPU
|
||||||
|
fields 0, minimum and maximum batch 1, one CPU thread, one IO slot and CPU task
|
||||||
|
class. These conservative coefficients cover Gate D containers, adapters,
|
||||||
|
camera/landmark state, Gate E copies, ordering/residual storage and Ceres
|
||||||
|
working storage. They are operational, machine-state independent and excluded
|
||||||
|
from scientific identity. A restored task uses its persisted generic estimate.
|
||||||
|
|
||||||
|
The four full-domain `uint64_t` payload values (`maximum_observations`,
|
||||||
|
`maximum_tracks` and both deterministic seeds) are individual exact eight-byte
|
||||||
|
little-endian SQL BLOBs. Any other storage class or length is corrupt; there is
|
||||||
|
no signed cast, text, REAL conversion or domain restriction. Their F0 encoding
|
||||||
|
remains unchanged.
|
||||||
|
|
||||||
|
Gate D `COMPLETE` and usable `PARTIAL` results proceed to Gate E. Gate D
|
||||||
|
scientific `FAILED` fails the Task, invokes no Gate E and publishes nothing.
|
||||||
|
Gate D invalid input or allocation failure likewise fails execution. A claimed
|
||||||
|
usable result that violates frozen structure is an internal integration
|
||||||
|
failure. Once Gate E is legitimately reached, every `EXECUTION_OK` scientific
|
||||||
|
status remains publication-eligible.
|
||||||
|
|
||||||
|
Gate F computes publication diagnostics from every retained observation of the
|
||||||
|
exact final Gate E result. With source-pixel residual `dx,dy`, each observation
|
||||||
|
contributes `s=dx*dx+dy*dy` and `e=sqrt(s)`. Global RMSE is
|
||||||
|
`sqrt(sum(s)/N)`. Global median is the middle sorted `e`, or for even `N`,
|
||||||
|
`lower + (upper-lower)/2`. Projection is the frozen Gate E distorted pinhole
|
||||||
|
model, binary64, with strict minimum depth. Empty, non-finite or invalid
|
||||||
|
projection input fails publication; no observation is skipped or clamped.
|
||||||
|
Metrics are deterministic result diagnostics, never candidate identity.
|
||||||
|
|
||||||
|
### Gate F durable reconstruction projection
|
||||||
|
|
||||||
|
**FROZEN.** Gate F projects the complete Gate D/E scientific result onto the
|
||||||
|
existing Project DB reconstruction model. A component belongs to the durable
|
||||||
|
projection exactly when `registered_image_count > 0` and `landmark_count > 0`.
|
||||||
|
Every such component is persisted with its exact final Gate E geometry. This
|
||||||
|
includes a BA-rejected component whose valid Gate D geometry Gate E preserved.
|
||||||
|
|
||||||
|
A non-reconstructed graph component failing either predicate remains an
|
||||||
|
ephemeral scientific/orchestration diagnostic and has no Project DB component
|
||||||
|
row. Gate F fabricates no camera, landmark or placeholder and does not fail an
|
||||||
|
otherwise publishable reconstruction merely because such diagnostics exist.
|
||||||
|
The projected result must still satisfy every top-level Project DB invariant;
|
||||||
|
otherwise publication does not occur and the Task fails.
|
||||||
|
|
||||||
|
Persisted component and geometry counts describe only this durable projection.
|
||||||
|
Global reprojection metrics likewise include exactly the retained observations
|
||||||
|
belonging to persisted geometry. Omission neither renumbers scientific
|
||||||
|
component keys nor changes candidate identity. No persistent diagnostic table,
|
||||||
|
sidecar, metadata blob or schema beyond v17 `sparse_sfm_tasks` is introduced.
|
||||||
|
|
||||||
|
Gate F v1 is **PASS / FROZEN**. Gate D and Gate E remain **PASS / FROZEN**;
|
||||||
|
Gate G remains open and planned.
|
||||||
|
|
||||||
|
### Gate F validation closure
|
||||||
|
|
||||||
|
**PASS / FROZEN.** Gate F freezes the F0 372-byte parameter record v1 and its
|
||||||
|
SHA-256 digest, Project DB v17 typed payload, `sparse_sfm.run` version 1,
|
||||||
|
pre-admission declarative estimate, governed Task Runtime execution, durable
|
||||||
|
replay, deterministic D→E orchestration, exact candidate reuse and atomic
|
||||||
|
reconstruction publication. Canonical gate progression:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Gate A — PASS / FROZEN
|
||||||
|
Gate B — PASS / FROZEN
|
||||||
|
Gate C — PASS / FROZEN
|
||||||
|
Gate D — PASS / FROZEN
|
||||||
|
Gate E — PASS / FROZEN
|
||||||
|
Gate F — PASS / FROZEN
|
||||||
|
Gate G — OPEN / PLANNED
|
||||||
|
```
|
||||||
|
|
||||||
|
The five candidate-identity dimensions remain separate:
|
||||||
|
|
||||||
|
```text
|
||||||
|
(
|
||||||
|
input_track_set_identity,
|
||||||
|
calibration_scope_identity,
|
||||||
|
sfm_kind,
|
||||||
|
sfm_version,
|
||||||
|
parameter_fingerprint
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
The F0 golden SHA-256 digest remains
|
||||||
|
`e1c83e5b2036e49254a9426ddbace42b7831373bc896f27abdd2f61e302f9e8c`.
|
||||||
|
Final validation completed with the normal suite at 41/41, targeted Gate F
|
||||||
|
ASan/UBSan/LeakSanitizer at 4/4 with leak detection enabled, and the full
|
||||||
|
sequential ASan/UBSan suite at 41/41. Fresh-process validation passed 20/20 for
|
||||||
|
the Gate F contract and 20/20 for the production `sparse_sfm.run` task. The C17
|
||||||
|
public-header probe and `git diff --check` passed. Final human diff review
|
||||||
|
passed; no Gate F implementation work, validation work or human decision
|
||||||
|
remains.
|
||||||
|
|
||||||
## Future persistence and API candidates
|
## Future persistence and API candidates
|
||||||
|
|
||||||
No Project DB v16 is created in Gate A. A later model gate may define immutable
|
No Project DB v16 is created in Gate A. A later model gate may define immutable
|
||||||
|
|
@ -831,21 +1010,21 @@ reported separately. Thread probes are limited to 1/2/4/8 threads and stop if
|
||||||
MemAvailable, swap, PSI or desktop responsiveness becomes unhealthy. No
|
MemAvailable, swap, PSI or desktop responsiveness becomes unhealthy. No
|
||||||
production Sparse SfM code is created by this gate.
|
production Sparse SfM code is created by this gate.
|
||||||
|
|
||||||
## Future gate plan
|
## Gate decomposition
|
||||||
|
|
||||||
- **Gate B — Sparse Reconstruction Model:** immutable in-memory model, result
|
- **Gate B — PASS / FROZEN — Sparse Reconstruction Model:** immutable in-memory model, result
|
||||||
states, calibration ownership and candidate persistence contract; no DB v16
|
states, calibration ownership and candidate persistence contract; no DB v16
|
||||||
until this contract is reviewed.
|
until this contract is reviewed.
|
||||||
- **Gate C — Geometry primitives:** normalized camera model, relative pose,
|
- **Gate C — PASS / FROZEN — Geometry primitives:** normalized camera model, relative pose,
|
||||||
deterministic seed, triangulation and PnP with synthetic ground truth.
|
deterministic seed, triangulation and PnP with synthetic ground truth.
|
||||||
- **Gate D — Incremental core:** registration ordering, components,
|
- **Gate D — PASS / FROZEN — Incremental core:** registration ordering, components,
|
||||||
unregistered-image policy and deterministic reconstruction output.
|
unregistered-image policy and deterministic reconstruction output.
|
||||||
- **Gate E — Final Bundle Adjustment:** synchronous final per-component BA on a
|
- **Gate E — PASS / FROZEN — Final Bundle Adjustment:** synchronous final per-component BA on a
|
||||||
copy of the immutable Gate D result, with its scientific and numerical
|
copy of the immutable Gate D result, with its scientific and numerical
|
||||||
contract frozen here; interleaved local BA is deferred.
|
contract frozen here; interleaved local BA is deferred.
|
||||||
- **Gate F — Project orchestration:** explicit Track Set/calibration input,
|
- **Gate F — PASS / FROZEN — Project orchestration:** explicit Track Set/calibration input,
|
||||||
atomic publication and durable runtime integration.
|
atomic publication and durable runtime integration.
|
||||||
- **Gate G — Resource/freeze:** Governor admission, sustained hardware safety,
|
- **Gate G — OPEN / PLANNED — Resource/freeze:** Governor admission, sustained hardware safety,
|
||||||
recovery, full validation and final freeze.
|
recovery, full validation and final freeze.
|
||||||
|
|
||||||
## Algorithm comparison and Gate A evidence
|
## Algorithm comparison and Gate A evidence
|
||||||
|
|
|
||||||
|
|
@ -79,3 +79,8 @@ générique v1 ne possède aucun payload propre au kind.
|
||||||
depuis son payload Project DB v15 et son asset little-endian validé. Le callback
|
depuis son payload Project DB v15 et son asset little-endian validé. Le callback
|
||||||
réutilise l'orchestration Gate C et le reconstructeur refuse toute corruption,
|
réutilise l'orchestration Gate C et le reconstructeur refuse toute corruption,
|
||||||
version, fingerprint, checksum, tri, unicité ou L3DTSIS1 incohérents.
|
version, fingerprint, checksum, tri, unicité ou L3DTSIS1 incohérents.
|
||||||
|
|
||||||
|
**PASS / FROZEN** — `sparse_sfm.run`, version 1, recharge le
|
||||||
|
payload scientifique explicite Project DB v17, restaure l'estimation générique
|
||||||
|
persistée et rejoue D puis E depuis les références Track Set/calibration. Le
|
||||||
|
fingerprint F0 est recalculé ; le checkpoint générique v1 reste inchangé.
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ Lardon3D suit une feuille de route ordonnée qui privilégie la stabilité et la
|
||||||
- ✅ Sparse SfM : primitives géométriques Gate C et noyau incrémental Gate D
|
- ✅ Sparse SfM : primitives géométriques Gate C et noyau incrémental Gate D
|
||||||
implémentés
|
implémentés
|
||||||
- ✅ Sparse SfM Gate E : Bundle Adjustment final par composante PASS / FROZEN
|
- ✅ Sparse SfM Gate E : Bundle Adjustment final par composante PASS / FROZEN
|
||||||
|
- ✅ Sparse SfM Gate F : orchestration durable et publication atomique PASS / FROZEN
|
||||||
|
|
||||||
### Phase 5 : Reconstruction (PLANNED)
|
### Phase 5 : Reconstruction (PLANNED)
|
||||||
- 📋 Orchestration de reconstruction incrémentale
|
- 📋 Orchestration de reconstruction incrémentale
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include <lardon3d/task.h>
|
#include <lardon3d/task.h>
|
||||||
|
#include <lardon3d/sparse_sfm_incremental.h>
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 16,
|
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 17,
|
||||||
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
||||||
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
||||||
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
||||||
|
|
@ -391,6 +392,15 @@ typedef struct {
|
||||||
int64_t created_at;
|
int64_t created_at;
|
||||||
} Lardon3DProjectDbTrackSet;
|
} Lardon3DProjectDbTrackSet;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t task_id;
|
||||||
|
uint64_t track_set_id;
|
||||||
|
uint64_t calibration_scope_id;
|
||||||
|
uint32_t sfm_kind;
|
||||||
|
uint32_t sfm_version;
|
||||||
|
Lardon3DSparseIncrementalParameters parameters;
|
||||||
|
} Lardon3DProjectDbSparseSfmTask;
|
||||||
|
|
||||||
void lardon3d_project_db_free_track(Lardon3DProjectDbTrack *track);
|
void lardon3d_project_db_free_track(Lardon3DProjectDbTrack *track);
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_create_track_set(
|
Lardon3DProjectDbResult lardon3d_project_db_create_track_set(
|
||||||
Lardon3DProjectDb *database, const Lardon3DProjectDbTrackSet *configuration,
|
Lardon3DProjectDb *database, const Lardon3DProjectDbTrackSet *configuration,
|
||||||
|
|
@ -602,6 +612,14 @@ Lardon3DProjectDbResult lardon3d_project_db_record_track_builder_task(
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_track_builder_task(
|
Lardon3DProjectDbResult lardon3d_project_db_load_track_builder_task(
|
||||||
Lardon3DProjectDb *database, uint64_t task_id,
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
Lardon3DProjectDbTrackBuilderTask *parameters);
|
Lardon3DProjectDbTrackBuilderTask *parameters);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_record_sparse_sfm_task(
|
||||||
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
|
const char *kind, uint32_t version,
|
||||||
|
const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||||
|
const Lardon3DProjectDbSparseSfmTask *parameters, int64_t updated_at);
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_sparse_sfm_task(
|
||||||
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
|
Lardon3DProjectDbSparseSfmTask *parameters);
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_create_match_result(
|
Lardon3DProjectDbResult lardon3d_project_db_create_match_result(
|
||||||
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,
|
Lardon3DProjectDb *database, uint64_t candidate_pair_id, uint64_t feature_set_id_a,
|
||||||
|
|
|
||||||
38
include/lardon3d/sparse_sfm_task.h
Normal file
38
include/lardon3d/sparse_sfm_task.h
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef LARDON3D_SPARSE_SFM_TASK_H
|
||||||
|
#define LARDON3D_SPARSE_SFM_TASK_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <lardon3d/app_state.h>
|
||||||
|
#include <lardon3d/sparse_sfm_incremental.h>
|
||||||
|
#include <lardon3d/task_kind_registry.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LARDON3D_SPARSE_SFM_TASK_KIND "sparse_sfm.run"
|
||||||
|
|
||||||
|
enum { LARDON3D_SPARSE_SFM_TASK_KIND_VERSION = 1 };
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t track_set_id;
|
||||||
|
uint64_t calibration_scope_id;
|
||||||
|
Lardon3DSparseIncrementalParameters parameters;
|
||||||
|
} Lardon3DSparseSfmTaskConfiguration;
|
||||||
|
|
||||||
|
Lardon3DTask *lardon3d_project_create_sparse_sfm_task(
|
||||||
|
Lardon3DAppState *state, const Lardon3DSparseSfmTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id);
|
||||||
|
bool lardon3d_project_enqueue_sparse_sfm_task(
|
||||||
|
Lardon3DAppState *state, const Lardon3DSparseSfmTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id);
|
||||||
|
bool lardon3d_sparse_sfm_task_reconstruct(const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
|
void *context, Lardon3DTaskKindBinding *binding);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
60
meson.build
60
meson.build
|
|
@ -153,6 +153,8 @@ executable(
|
||||||
'src/sparse_sfm_geometry.cpp',
|
'src/sparse_sfm_geometry.cpp',
|
||||||
'src/sparse_sfm_incremental.cpp',
|
'src/sparse_sfm_incremental.cpp',
|
||||||
'src/sparse_sfm_bundle_adjustment.cpp',
|
'src/sparse_sfm_bundle_adjustment.cpp',
|
||||||
|
'src/sparse_sfm_gate_f.cpp',
|
||||||
|
'src/sparse_sfm_task.cpp',
|
||||||
'src/task.c',
|
'src/task.c',
|
||||||
'src/task_checkpoint.c',
|
'src/task_checkpoint.c',
|
||||||
'src/task_kind_registry.c',
|
'src/task_kind_registry.c',
|
||||||
|
|
@ -169,7 +171,10 @@ executable(
|
||||||
'src/track_builder_task.cpp',
|
'src/track_builder_task.cpp',
|
||||||
] + matcher_backend_sources,
|
] + matcher_backend_sources,
|
||||||
include_directories: include_directories('include'),
|
include_directories: include_directories('include'),
|
||||||
c_args: ['-DLARDON3D_TRACK_BUILDER_TASK_AVAILABLE'],
|
c_args: [
|
||||||
|
'-DLARDON3D_TRACK_BUILDER_TASK_AVAILABLE',
|
||||||
|
'-DLARDON3D_SPARSE_SFM_TASK_AVAILABLE',
|
||||||
|
],
|
||||||
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry, ceres]
|
dependencies: [ncursesw, threads, sqlite3, openssl, opencv, opencv_geometry, ceres]
|
||||||
+ matcher_backend_dependencies,
|
+ matcher_backend_dependencies,
|
||||||
)
|
)
|
||||||
|
|
@ -918,6 +923,59 @@ geometric_verifier_task_test = executable(
|
||||||
test('geometric-verifier-task', geometric_verifier_task_test, timeout: 60,
|
test('geometric-verifier-task', geometric_verifier_task_test, timeout: 60,
|
||||||
env: opencv_test_environment)
|
env: opencv_test_environment)
|
||||||
|
|
||||||
|
sparse_sfm_gate_f_test = executable(
|
||||||
|
'test-sparse-sfm-gate-f',
|
||||||
|
sources: [
|
||||||
|
'tests/test_sparse_sfm_gate_f.cpp',
|
||||||
|
'src/sparse_sfm_gate_f.cpp',
|
||||||
|
'src/sparse_sfm_incremental.cpp',
|
||||||
|
'src/sparse_sfm_geometry.cpp',
|
||||||
|
],
|
||||||
|
include_directories: include_directories('include'),
|
||||||
|
dependencies: [openssl, opencv_geometry],
|
||||||
|
)
|
||||||
|
test('sparse-sfm-gate-f', sparse_sfm_gate_f_test, timeout: 60)
|
||||||
|
|
||||||
|
sparse_sfm_task_test = executable(
|
||||||
|
'test-sparse-sfm-task',
|
||||||
|
sources: [
|
||||||
|
'tests/test_sparse_sfm_task.cpp',
|
||||||
|
'src/sparse_sfm_task.cpp',
|
||||||
|
'src/sparse_sfm_gate_f.cpp',
|
||||||
|
'src/sparse_sfm_incremental.cpp',
|
||||||
|
'src/sparse_sfm_geometry.cpp',
|
||||||
|
'src/sparse_sfm_bundle_adjustment.cpp',
|
||||||
|
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||||
|
'src/feature_store.c', 'src/feature_extractor_opencv.cpp',
|
||||||
|
'src/app_state.c',
|
||||||
|
'src/task.c', 'src/task_checkpoint.c', 'src/task_kind_registry.c',
|
||||||
|
'src/task_queue.c',
|
||||||
|
'src/resource_governor.c', 'src/resource_snapshot.c',
|
||||||
|
],
|
||||||
|
cpp_args: ['-DLARDON3D_SPARSE_SFM_TASK_TESTING'],
|
||||||
|
include_directories: include_directories('include'),
|
||||||
|
dependencies: [threads, sqlite3, openssl, opencv, opencv_geometry, ceres],
|
||||||
|
)
|
||||||
|
test('sparse-sfm-task', sparse_sfm_task_test, timeout: 120)
|
||||||
|
|
||||||
|
sparse_sfm_payload_test = executable(
|
||||||
|
'test-sparse-sfm-payload',
|
||||||
|
sources: [
|
||||||
|
'tests/test_sparse_sfm_payload.cpp',
|
||||||
|
'src/project_db.c',
|
||||||
|
'src/project_db_sparse_sfm.c',
|
||||||
|
'src/task.c',
|
||||||
|
'src/task_kind_registry.c',
|
||||||
|
'src/resource_governor.c',
|
||||||
|
'src/resource_snapshot.c',
|
||||||
|
'src/sparse_sfm_incremental.cpp',
|
||||||
|
'src/sparse_sfm_geometry.cpp',
|
||||||
|
],
|
||||||
|
include_directories: include_directories('include'),
|
||||||
|
dependencies: [threads, sqlite3, openssl, opencv_geometry],
|
||||||
|
)
|
||||||
|
test('sparse-sfm-payload', sparse_sfm_payload_test, timeout: 60)
|
||||||
|
|
||||||
track_builder_test = executable(
|
track_builder_test = executable(
|
||||||
'test-track-builder-core',
|
'test-track-builder-core',
|
||||||
sources: ['tests/test_track_builder_core.cpp', 'src/track_builder.cpp'],
|
sources: ['tests/test_track_builder_core.cpp', 'src/track_builder.cpp'],
|
||||||
|
|
|
||||||
310
src/project_db.c
310
src/project_db.c
|
|
@ -423,6 +423,30 @@ static const char schema_sparse_sfm_v16_b[] =
|
||||||
"CREATE INDEX sparse_landmark_observations_page_idx ON "
|
"CREATE INDEX sparse_landmark_observations_page_idx ON "
|
||||||
"sparse_landmark_observations(landmark_id,position_in_track);";
|
"sparse_landmark_observations(landmark_id,position_in_track);";
|
||||||
|
|
||||||
|
static const char schema_sparse_sfm_task_v17[] =
|
||||||
|
"CREATE TABLE sparse_sfm_tasks("
|
||||||
|
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
|
||||||
|
"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),"
|
||||||
|
"minimum_seed_tracks INTEGER NOT NULL,minimum_seed_landmarks INTEGER NOT NULL,"
|
||||||
|
"minimum_pnp_correspondences INTEGER NOT NULL,maximum_seed_candidates INTEGER NOT NULL,"
|
||||||
|
"maximum_registration_rounds INTEGER NOT NULL,maximum_landmarks_per_round INTEGER NOT NULL,"
|
||||||
|
"maximum_images INTEGER NOT NULL,maximum_observations BLOB NOT NULL "
|
||||||
|
"CHECK(length(maximum_observations)=8),maximum_tracks BLOB NOT NULL "
|
||||||
|
"CHECK(length(maximum_tracks)=8),reprojection_threshold_px REAL NOT NULL,"
|
||||||
|
"minimum_track_parallax_rad REAL NOT NULL,relative_robust_threshold_px REAL NOT NULL,"
|
||||||
|
"relative_confidence REAL NOT NULL,relative_max_iterations INTEGER NOT NULL,"
|
||||||
|
"relative_minimum_inliers INTEGER NOT NULL,relative_minimum_inlier_ratio REAL NOT NULL,"
|
||||||
|
"relative_minimum_parallax_rad REAL NOT NULL,relative_minimum_cheirality_ratio REAL NOT NULL,"
|
||||||
|
"relative_deterministic_seed BLOB NOT NULL CHECK(length(relative_deterministic_seed)=8),"
|
||||||
|
"pnp_reprojection_threshold_px REAL NOT NULL,pnp_confidence REAL NOT NULL,"
|
||||||
|
"pnp_max_iterations INTEGER NOT NULL,pnp_minimum_inliers INTEGER NOT NULL,"
|
||||||
|
"pnp_minimum_inlier_ratio REAL NOT NULL,pnp_deterministic_seed BLOB NOT NULL "
|
||||||
|
"CHECK(length(pnp_deterministic_seed)=8),refinement_max_iterations INTEGER NOT NULL,"
|
||||||
|
"refinement_convergence_tolerance REAL NOT NULL);";
|
||||||
|
|
||||||
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
||||||
if (destination) {
|
if (destination) {
|
||||||
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
|
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
|
||||||
|
|
@ -839,6 +863,23 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
||||||
"finish schema v16 migration");
|
"finish schema v16 migration");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && from_version < 17) {
|
||||||
|
result = execute(database, schema_sparse_sfm_task_v17,
|
||||||
|
"migrate schema v16 to v17");
|
||||||
|
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V17")) {
|
||||||
|
result = execute(database, "INSERT INTO missing_v17_test_table VALUES(1)",
|
||||||
|
"forced migration v17 failure");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = execute(
|
||||||
|
database,
|
||||||
|
"UPDATE metadata SET value=17 WHERE key='schema_version' AND value=16",
|
||||||
|
"finish schema v17 migration");
|
||||||
|
}
|
||||||
|
}
|
||||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
result = execute(database, "COMMIT", "commit migration");
|
result = execute(database, "COMMIT", "commit migration");
|
||||||
}
|
}
|
||||||
|
|
@ -1150,6 +1191,36 @@ static bool valid_durable_task(const Lardon3DTaskDurableSnapshot *snapshot, int6
|
||||||
updated_at >= 0;
|
updated_at >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool valid_sparse_sfm_parameters(const Lardon3DSparseIncrementalParameters *p) {
|
||||||
|
return p && p->minimum_seed_tracks >= 2 && p->minimum_seed_landmarks > 0 &&
|
||||||
|
p->minimum_pnp_correspondences >= 4 && p->maximum_seed_candidates > 0 &&
|
||||||
|
p->maximum_registration_rounds > 0 && p->maximum_landmarks_per_round > 0 &&
|
||||||
|
p->maximum_images >= 2 && p->maximum_observations >= p->maximum_tracks &&
|
||||||
|
isfinite(p->reprojection_threshold_px) && p->reprojection_threshold_px > 0.0 &&
|
||||||
|
isfinite(p->minimum_track_parallax_rad) && p->minimum_track_parallax_rad >= 0.0 &&
|
||||||
|
isfinite(p->relative_pose.robust_threshold_px) &&
|
||||||
|
p->relative_pose.robust_threshold_px > 0.0 && isfinite(p->relative_pose.confidence) &&
|
||||||
|
p->relative_pose.confidence > 0.0 && p->relative_pose.confidence < 1.0 &&
|
||||||
|
p->relative_pose.max_iterations > 0 &&
|
||||||
|
p->relative_pose.minimum_inliers >= p->minimum_seed_tracks &&
|
||||||
|
isfinite(p->relative_pose.minimum_inlier_ratio) &&
|
||||||
|
p->relative_pose.minimum_inlier_ratio > 0.0 &&
|
||||||
|
p->relative_pose.minimum_inlier_ratio <= 1.0 &&
|
||||||
|
isfinite(p->relative_pose.minimum_parallax_rad) &&
|
||||||
|
p->relative_pose.minimum_parallax_rad >= 0.0 &&
|
||||||
|
isfinite(p->relative_pose.minimum_cheirality_ratio) &&
|
||||||
|
p->relative_pose.minimum_cheirality_ratio > 0.0 &&
|
||||||
|
p->relative_pose.minimum_cheirality_ratio <= 1.0 &&
|
||||||
|
isfinite(p->pnp.reprojection_threshold_px) && p->pnp.reprojection_threshold_px > 0.0 &&
|
||||||
|
isfinite(p->pnp.confidence) && p->pnp.confidence > 0.0 && p->pnp.confidence < 1.0 &&
|
||||||
|
p->pnp.max_iterations > 0 &&
|
||||||
|
p->pnp.minimum_inliers >= p->minimum_pnp_correspondences &&
|
||||||
|
isfinite(p->pnp.minimum_inlier_ratio) && p->pnp.minimum_inlier_ratio > 0.0 &&
|
||||||
|
p->pnp.minimum_inlier_ratio <= 1.0 && p->refinement.max_iterations > 0 &&
|
||||||
|
isfinite(p->refinement.convergence_tolerance) &&
|
||||||
|
p->refinement.convergence_tolerance > 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
static Lardon3DProjectDbResult
|
static Lardon3DProjectDbResult
|
||||||
record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
const char *task_kind, uint32_t task_kind_version,
|
const char *task_kind, uint32_t task_kind_version,
|
||||||
|
|
@ -1160,6 +1231,7 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps
|
||||||
const Lardon3DProjectDbCandidatePairGenerateTask *candidate_pair,
|
const Lardon3DProjectDbCandidatePairGenerateTask *candidate_pair,
|
||||||
const Lardon3DProjectDbMatcherTask *matcher,
|
const Lardon3DProjectDbMatcherTask *matcher,
|
||||||
const Lardon3DProjectDbGeometricVerifierTask *geometric_verifier,
|
const Lardon3DProjectDbGeometricVerifierTask *geometric_verifier,
|
||||||
|
const Lardon3DProjectDbSparseSfmTask *sparse_sfm,
|
||||||
int64_t updated_at) {
|
int64_t updated_at) {
|
||||||
bool typed = task_kind != NULL;
|
bool typed = task_kind != NULL;
|
||||||
if (!database || !valid_durable_task(snapshot, updated_at) ||
|
if (!database || !valid_durable_task(snapshot, updated_at) ||
|
||||||
|
|
@ -1220,6 +1292,12 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps
|
||||||
geometric_verifier->min_inlier_ratio > 1.0 ||
|
geometric_verifier->min_inlier_ratio > 1.0 ||
|
||||||
geometric_verifier->seed_policy_version == 0 ||
|
geometric_verifier->seed_policy_version == 0 ||
|
||||||
geometric_verifier->canonicalization_version == 0)) ||
|
geometric_verifier->canonicalization_version == 0)) ||
|
||||||
|
(sparse_sfm &&
|
||||||
|
(!task_kind || strcmp(task_kind, "sparse_sfm.run") != 0 || task_kind_version != 1 ||
|
||||||
|
!valid_task_id(sparse_sfm->task_id) || sparse_sfm->task_id != snapshot->id ||
|
||||||
|
!valid_task_id(sparse_sfm->track_set_id) ||
|
||||||
|
!valid_task_id(sparse_sfm->calibration_scope_id) || sparse_sfm->sfm_kind != 1 ||
|
||||||
|
sparse_sfm->sfm_version != 1 || !valid_sparse_sfm_parameters(&sparse_sfm->parameters))) ||
|
||||||
(checkpoint && !valid_checkpoint(checkpoint))) {
|
(checkpoint && !valid_checkpoint(checkpoint))) {
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
|
|
@ -1489,6 +1567,103 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && sparse_sfm) {
|
||||||
|
const Lardon3DSparseIncrementalParameters *p = &sparse_sfm->parameters;
|
||||||
|
unsigned char maximum_observations[8];
|
||||||
|
unsigned char maximum_tracks[8];
|
||||||
|
unsigned char relative_seed[8];
|
||||||
|
unsigned char pnp_seed[8];
|
||||||
|
for (unsigned int i = 0; i < 8; ++i) {
|
||||||
|
maximum_observations[i] = (unsigned char)(p->maximum_observations >> (8U * i));
|
||||||
|
maximum_tracks[i] = (unsigned char)(p->maximum_tracks >> (8U * i));
|
||||||
|
relative_seed[i] = (unsigned char)(p->relative_pose.deterministic_seed >> (8U * i));
|
||||||
|
pnp_seed[i] = (unsigned char)(p->pnp.deterministic_seed >> (8U * i));
|
||||||
|
}
|
||||||
|
result = prepare(
|
||||||
|
database,
|
||||||
|
"INSERT INTO sparse_sfm_tasks(task_id,track_set_id,calibration_scope_id,sfm_kind,"
|
||||||
|
"sfm_version,minimum_seed_tracks,minimum_seed_landmarks,minimum_pnp_correspondences,"
|
||||||
|
"maximum_seed_candidates,maximum_registration_rounds,maximum_landmarks_per_round,"
|
||||||
|
"maximum_images,maximum_observations,maximum_tracks,reprojection_threshold_px,"
|
||||||
|
"minimum_track_parallax_rad,relative_robust_threshold_px,relative_confidence,"
|
||||||
|
"relative_max_iterations,relative_minimum_inliers,relative_minimum_inlier_ratio,"
|
||||||
|
"relative_minimum_parallax_rad,relative_minimum_cheirality_ratio,"
|
||||||
|
"relative_deterministic_seed,pnp_reprojection_threshold_px,pnp_confidence,"
|
||||||
|
"pnp_max_iterations,pnp_minimum_inliers,pnp_minimum_inlier_ratio,"
|
||||||
|
"pnp_deterministic_seed,refinement_max_iterations,refinement_convergence_tolerance) "
|
||||||
|
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15,?16,?17,?18,?19,"
|
||||||
|
"?20,?21,?22,?23,?24,?25,?26,?27,?28,?29,?30,?31,?32) ON CONFLICT(task_id) DO "
|
||||||
|
"UPDATE SET track_set_id=excluded.track_set_id WHERE sparse_sfm_tasks.track_set_id="
|
||||||
|
"excluded.track_set_id AND sparse_sfm_tasks.calibration_scope_id="
|
||||||
|
"excluded.calibration_scope_id AND sparse_sfm_tasks.sfm_kind=excluded.sfm_kind AND "
|
||||||
|
"sparse_sfm_tasks.sfm_version=excluded.sfm_version AND sparse_sfm_tasks."
|
||||||
|
"minimum_seed_tracks=excluded.minimum_seed_tracks AND sparse_sfm_tasks."
|
||||||
|
"minimum_seed_landmarks=excluded.minimum_seed_landmarks AND sparse_sfm_tasks."
|
||||||
|
"minimum_pnp_correspondences=excluded.minimum_pnp_correspondences AND sparse_sfm_tasks."
|
||||||
|
"maximum_seed_candidates=excluded.maximum_seed_candidates AND sparse_sfm_tasks."
|
||||||
|
"maximum_registration_rounds=excluded.maximum_registration_rounds AND sparse_sfm_tasks."
|
||||||
|
"maximum_landmarks_per_round=excluded.maximum_landmarks_per_round AND sparse_sfm_tasks."
|
||||||
|
"maximum_images=excluded.maximum_images AND sparse_sfm_tasks.maximum_observations="
|
||||||
|
"excluded.maximum_observations AND sparse_sfm_tasks.maximum_tracks=excluded.maximum_tracks "
|
||||||
|
"AND sparse_sfm_tasks.reprojection_threshold_px=excluded.reprojection_threshold_px AND "
|
||||||
|
"sparse_sfm_tasks.minimum_track_parallax_rad=excluded.minimum_track_parallax_rad AND "
|
||||||
|
"sparse_sfm_tasks.relative_robust_threshold_px=excluded.relative_robust_threshold_px AND "
|
||||||
|
"sparse_sfm_tasks.relative_confidence=excluded.relative_confidence AND sparse_sfm_tasks."
|
||||||
|
"relative_max_iterations=excluded.relative_max_iterations AND sparse_sfm_tasks."
|
||||||
|
"relative_minimum_inliers=excluded.relative_minimum_inliers AND sparse_sfm_tasks."
|
||||||
|
"relative_minimum_inlier_ratio=excluded.relative_minimum_inlier_ratio AND sparse_sfm_tasks."
|
||||||
|
"relative_minimum_parallax_rad=excluded.relative_minimum_parallax_rad AND sparse_sfm_tasks."
|
||||||
|
"relative_minimum_cheirality_ratio=excluded.relative_minimum_cheirality_ratio AND "
|
||||||
|
"sparse_sfm_tasks.relative_deterministic_seed=excluded.relative_deterministic_seed AND "
|
||||||
|
"sparse_sfm_tasks.pnp_reprojection_threshold_px=excluded.pnp_reprojection_threshold_px AND "
|
||||||
|
"sparse_sfm_tasks.pnp_confidence=excluded.pnp_confidence AND sparse_sfm_tasks."
|
||||||
|
"pnp_max_iterations=excluded.pnp_max_iterations AND sparse_sfm_tasks."
|
||||||
|
"pnp_minimum_inliers=excluded.pnp_minimum_inliers AND sparse_sfm_tasks."
|
||||||
|
"pnp_minimum_inlier_ratio=excluded.pnp_minimum_inlier_ratio AND sparse_sfm_tasks."
|
||||||
|
"pnp_deterministic_seed=excluded.pnp_deterministic_seed AND sparse_sfm_tasks."
|
||||||
|
"refinement_max_iterations=excluded.refinement_max_iterations AND sparse_sfm_tasks."
|
||||||
|
"refinement_convergence_tolerance=excluded.refinement_convergence_tolerance",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)sparse_sfm->task_id);
|
||||||
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)sparse_sfm->track_set_id);
|
||||||
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)sparse_sfm->calibration_scope_id);
|
||||||
|
sqlite3_bind_int(statement, 4, (int)sparse_sfm->sfm_kind);
|
||||||
|
sqlite3_bind_int(statement, 5, (int)sparse_sfm->sfm_version);
|
||||||
|
sqlite3_bind_int64(statement, 6, p->minimum_seed_tracks);
|
||||||
|
sqlite3_bind_int64(statement, 7, p->minimum_seed_landmarks);
|
||||||
|
sqlite3_bind_int64(statement, 8, p->minimum_pnp_correspondences);
|
||||||
|
sqlite3_bind_int64(statement, 9, p->maximum_seed_candidates);
|
||||||
|
sqlite3_bind_int64(statement, 10, p->maximum_registration_rounds);
|
||||||
|
sqlite3_bind_int64(statement, 11, p->maximum_landmarks_per_round);
|
||||||
|
sqlite3_bind_int64(statement, 12, p->maximum_images);
|
||||||
|
sqlite3_bind_blob(statement, 13, maximum_observations, 8, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_blob(statement, 14, maximum_tracks, 8, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_double(statement, 15, p->reprojection_threshold_px);
|
||||||
|
sqlite3_bind_double(statement, 16, p->minimum_track_parallax_rad);
|
||||||
|
sqlite3_bind_double(statement, 17, p->relative_pose.robust_threshold_px);
|
||||||
|
sqlite3_bind_double(statement, 18, p->relative_pose.confidence);
|
||||||
|
sqlite3_bind_int64(statement, 19, p->relative_pose.max_iterations);
|
||||||
|
sqlite3_bind_int64(statement, 20, p->relative_pose.minimum_inliers);
|
||||||
|
sqlite3_bind_double(statement, 21, p->relative_pose.minimum_inlier_ratio);
|
||||||
|
sqlite3_bind_double(statement, 22, p->relative_pose.minimum_parallax_rad);
|
||||||
|
sqlite3_bind_double(statement, 23, p->relative_pose.minimum_cheirality_ratio);
|
||||||
|
sqlite3_bind_blob(statement, 24, relative_seed, 8, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_double(statement, 25, p->pnp.reprojection_threshold_px);
|
||||||
|
sqlite3_bind_double(statement, 26, p->pnp.confidence);
|
||||||
|
sqlite3_bind_int64(statement, 27, p->pnp.max_iterations);
|
||||||
|
sqlite3_bind_int64(statement, 28, p->pnp.minimum_inliers);
|
||||||
|
sqlite3_bind_double(statement, 29, p->pnp.minimum_inlier_ratio);
|
||||||
|
sqlite3_bind_blob(statement, 30, pnp_seed, 8, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_int64(statement, 31, p->refinement.max_iterations);
|
||||||
|
sqlite3_bind_double(statement, 32, p->refinement.convergence_tolerance);
|
||||||
|
result = step_done(database, statement, "upsert sparse SfM task");
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
||||||
|
copy_error(database->error, "Payload Sparse SfM immuable.");
|
||||||
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (result == LARDON3D_PROJECT_DB_OK && checkpoint) {
|
if (result == LARDON3D_PROJECT_DB_OK && checkpoint) {
|
||||||
result =
|
result =
|
||||||
prepare(database,
|
prepare(database,
|
||||||
|
|
@ -1521,7 +1696,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_task(
|
||||||
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, const char *task_kind,
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, const char *task_kind,
|
||||||
uint32_t task_kind_version, const Lardon3DProjectDbCheckpoint *checkpoint, int64_t updated_at) {
|
uint32_t task_kind_version, const Lardon3DProjectDbCheckpoint *checkpoint, int64_t updated_at) {
|
||||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
||||||
NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task(
|
Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task(
|
||||||
|
|
@ -1533,7 +1708,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task(
|
||||||
}
|
}
|
||||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint,
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint,
|
||||||
source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL,
|
source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||||
updated_at);
|
NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task(
|
Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task(
|
||||||
|
|
@ -1544,7 +1719,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task(
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
||||||
parameters, NULL, NULL, NULL, NULL, NULL, updated_at);
|
parameters, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task(
|
Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task(
|
||||||
|
|
@ -1554,7 +1729,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task(
|
||||||
const Lardon3DProjectDbSiftExtractTask *parameters, int64_t updated_at) {
|
const Lardon3DProjectDbSiftExtractTask *parameters, int64_t updated_at) {
|
||||||
if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
||||||
NULL, parameters, NULL, NULL, NULL, NULL, updated_at);
|
NULL, parameters, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool read_task(sqlite3_stmt *statement, Lardon3DProjectDbTask *task) {
|
static bool read_task(sqlite3_stmt *statement, Lardon3DProjectDbTask *task) {
|
||||||
|
|
@ -3536,7 +3711,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_visual_index_update_task(
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||||
NULL, parameters, NULL, NULL, NULL, updated_at);
|
NULL, parameters, NULL, NULL, NULL, NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_visual_index_update_task(
|
Lardon3DProjectDbResult lardon3d_project_db_load_visual_index_update_task(
|
||||||
|
|
@ -3583,7 +3758,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_candidate_pair_generate_task(
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||||
NULL, NULL, parameters, NULL, NULL, updated_at);
|
NULL, NULL, parameters, NULL, NULL, NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_candidate_pair_generate_task(
|
Lardon3DProjectDbResult lardon3d_project_db_load_candidate_pair_generate_task(
|
||||||
|
|
@ -3639,7 +3814,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_matcher_task(
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||||
NULL, NULL, NULL, parameters, NULL, updated_at);
|
NULL, NULL, NULL, parameters, NULL, NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_matcher_task(
|
Lardon3DProjectDbResult lardon3d_project_db_load_matcher_task(
|
||||||
|
|
@ -3697,7 +3872,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_geometric_verifier_task(
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||||
NULL, NULL, NULL, NULL, parameters, updated_at);
|
NULL, NULL, NULL, NULL, parameters, NULL, updated_at);
|
||||||
}
|
}
|
||||||
|
|
||||||
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
|
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
|
||||||
|
|
@ -3769,7 +3944,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_track_builder_task(
|
||||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
Lardon3DProjectDbResult result = record_task_internal(
|
Lardon3DProjectDbResult result = record_task_internal(
|
||||||
db, snapshot, kind, version, checkpoint, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
|
db, snapshot, kind, version, checkpoint, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||||
updated_at);
|
updated_at);
|
||||||
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
||||||
(void)pthread_mutex_lock(&db->mutex);
|
(void)pthread_mutex_lock(&db->mutex);
|
||||||
|
|
@ -3875,6 +4050,123 @@ Lardon3DProjectDbResult lardon3d_project_db_load_track_builder_task(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_record_sparse_sfm_task(
|
||||||
|
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||||
|
const char *kind, uint32_t version, const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||||
|
const Lardon3DProjectDbSparseSfmTask *parameters, int64_t updated_at) {
|
||||||
|
if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
return record_task_internal(database, snapshot, kind, version, checkpoint, NULL, 0, NULL, NULL,
|
||||||
|
NULL, NULL, NULL, NULL, parameters, updated_at);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read_u64_blob(sqlite3_stmt *statement, int column, uint64_t *value) {
|
||||||
|
if (sqlite3_column_type(statement, column) != SQLITE_BLOB ||
|
||||||
|
sqlite3_column_bytes(statement, column) != 8) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const unsigned char *bytes = sqlite3_column_blob(statement, column);
|
||||||
|
uint64_t decoded = 0;
|
||||||
|
for (unsigned int i = 0; i < 8; ++i) decoded |= (uint64_t)bytes[i] << (8U * i);
|
||||||
|
*value = decoded;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DProjectDbResult lardon3d_project_db_load_sparse_sfm_task(
|
||||||
|
Lardon3DProjectDb *database, uint64_t task_id,
|
||||||
|
Lardon3DProjectDbSparseSfmTask *parameters) {
|
||||||
|
if (!database || !valid_task_id(task_id) || !parameters) {
|
||||||
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
memset(parameters, 0, sizeof(*parameters));
|
||||||
|
(void)pthread_mutex_lock(&database->mutex);
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
Lardon3DProjectDbResult result = prepare(
|
||||||
|
database,
|
||||||
|
"SELECT s.track_set_id,s.calibration_scope_id,s.sfm_kind,s.sfm_version,"
|
||||||
|
"s.minimum_seed_tracks,s.minimum_seed_landmarks,s.minimum_pnp_correspondences,"
|
||||||
|
"s.maximum_seed_candidates,s.maximum_registration_rounds,s.maximum_landmarks_per_round,"
|
||||||
|
"s.maximum_images,s.maximum_observations,s.maximum_tracks,s.reprojection_threshold_px,"
|
||||||
|
"s.minimum_track_parallax_rad,s.relative_robust_threshold_px,s.relative_confidence,"
|
||||||
|
"s.relative_max_iterations,s.relative_minimum_inliers,s.relative_minimum_inlier_ratio,"
|
||||||
|
"s.relative_minimum_parallax_rad,s.relative_minimum_cheirality_ratio,"
|
||||||
|
"s.relative_deterministic_seed,s.pnp_reprojection_threshold_px,s.pnp_confidence,"
|
||||||
|
"s.pnp_max_iterations,s.pnp_minimum_inliers,s.pnp_minimum_inlier_ratio,"
|
||||||
|
"s.pnp_deterministic_seed,s.refinement_max_iterations,s.refinement_convergence_tolerance,"
|
||||||
|
"t.task_kind,t.task_kind_version FROM sparse_sfm_tasks s JOIN tasks t ON t.task_id=s.task_id "
|
||||||
|
"WHERE s.task_id=?1",
|
||||||
|
&statement);
|
||||||
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
||||||
|
int code = sqlite3_step(statement);
|
||||||
|
bool corrupt = code != SQLITE_ROW;
|
||||||
|
if (code == SQLITE_DONE) {
|
||||||
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||||
|
} else if (!corrupt) {
|
||||||
|
const char *kind = (const char *)sqlite3_column_text(statement, 31);
|
||||||
|
sqlite3_int64 ids[2] = {sqlite3_column_int64(statement, 0),
|
||||||
|
sqlite3_column_int64(statement, 1)};
|
||||||
|
sqlite3_int64 integers[15];
|
||||||
|
const int integer_columns[15] = {2, 3, 4, 5, 6, 7, 8, 9, 10, 17, 18, 25, 26, 29, 32};
|
||||||
|
for (size_t i = 0; i < 15; ++i) integers[i] = sqlite3_column_int64(statement,
|
||||||
|
integer_columns[i]);
|
||||||
|
double doubles[11];
|
||||||
|
const int double_columns[11] = {13, 14, 15, 16, 19, 20, 21, 23, 24, 27, 30};
|
||||||
|
for (size_t i = 0; i < 11; ++i) doubles[i] = sqlite3_column_double(statement,
|
||||||
|
double_columns[i]);
|
||||||
|
corrupt = ids[0] <= 0 || ids[1] <= 0 || !kind || strcmp(kind, "sparse_sfm.run") != 0 ||
|
||||||
|
integers[0] != 1 || integers[1] != 1 || integers[14] != 1;
|
||||||
|
for (size_t i = 2; i < 14; ++i) {
|
||||||
|
if (integers[i] <= 0 || integers[i] > UINT32_MAX) corrupt = true;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < 11; ++i) {
|
||||||
|
if (sqlite3_column_type(statement, double_columns[i]) != SQLITE_FLOAT ||
|
||||||
|
!isfinite(doubles[i])) {
|
||||||
|
corrupt = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parameters->task_id = task_id;
|
||||||
|
parameters->track_set_id = (uint64_t)ids[0];
|
||||||
|
parameters->calibration_scope_id = (uint64_t)ids[1];
|
||||||
|
parameters->sfm_kind = (uint32_t)integers[0];
|
||||||
|
parameters->sfm_version = (uint32_t)integers[1];
|
||||||
|
Lardon3DSparseIncrementalParameters *p = ¶meters->parameters;
|
||||||
|
p->minimum_seed_tracks = (uint32_t)integers[2];
|
||||||
|
p->minimum_seed_landmarks = (uint32_t)integers[3];
|
||||||
|
p->minimum_pnp_correspondences = (uint32_t)integers[4];
|
||||||
|
p->maximum_seed_candidates = (uint32_t)integers[5];
|
||||||
|
p->maximum_registration_rounds = (uint32_t)integers[6];
|
||||||
|
p->maximum_landmarks_per_round = (uint32_t)integers[7];
|
||||||
|
p->maximum_images = (uint32_t)integers[8];
|
||||||
|
corrupt |= !read_u64_blob(statement, 11, &p->maximum_observations);
|
||||||
|
corrupt |= !read_u64_blob(statement, 12, &p->maximum_tracks);
|
||||||
|
p->reprojection_threshold_px = doubles[0];
|
||||||
|
p->minimum_track_parallax_rad = doubles[1];
|
||||||
|
p->relative_pose.robust_threshold_px = doubles[2];
|
||||||
|
p->relative_pose.confidence = doubles[3];
|
||||||
|
p->relative_pose.max_iterations = (uint32_t)integers[9];
|
||||||
|
p->relative_pose.minimum_inliers = (uint32_t)integers[10];
|
||||||
|
p->relative_pose.minimum_inlier_ratio = doubles[4];
|
||||||
|
p->relative_pose.minimum_parallax_rad = doubles[5];
|
||||||
|
p->relative_pose.minimum_cheirality_ratio = doubles[6];
|
||||||
|
corrupt |= !read_u64_blob(statement, 22, &p->relative_pose.deterministic_seed);
|
||||||
|
p->pnp.reprojection_threshold_px = doubles[7];
|
||||||
|
p->pnp.confidence = doubles[8];
|
||||||
|
p->pnp.max_iterations = (uint32_t)integers[11];
|
||||||
|
p->pnp.minimum_inliers = (uint32_t)integers[12];
|
||||||
|
p->pnp.minimum_inlier_ratio = doubles[9];
|
||||||
|
corrupt |= !read_u64_blob(statement, 28, &p->pnp.deterministic_seed);
|
||||||
|
p->refinement.max_iterations = (uint32_t)integers[13];
|
||||||
|
p->refinement.convergence_tolerance = doubles[10];
|
||||||
|
if (corrupt || !valid_sparse_sfm_parameters(p)) result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||||
|
} else {
|
||||||
|
result = sqlite_result(database, code, "read sparse SfM task");
|
||||||
|
}
|
||||||
|
sqlite3_finalize(statement);
|
||||||
|
}
|
||||||
|
(void)pthread_mutex_unlock(&database->mutex);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
static bool read_match_result(sqlite3_stmt *statement,
|
static bool read_match_result(sqlite3_stmt *statement,
|
||||||
Lardon3DProjectDbMatchResult *result) {
|
Lardon3DProjectDbMatchResult *result) {
|
||||||
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
||||||
|
|
|
||||||
152
src/sparse_sfm_gate_f.cpp
Normal file
152
src/sparse_sfm_gate_f.cpp
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
#include "sparse_sfm_gate_f_internal.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool put_u32(unsigned char *record, size_t *offset, uint32_t value) {
|
||||||
|
if (*offset > 372U - 4U) return false;
|
||||||
|
for (unsigned int byte = 0; byte < 4; ++byte)
|
||||||
|
record[(*offset)++] = static_cast<unsigned char>(value >> (byte * 8U));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool put_u64(unsigned char *record, size_t *offset, uint64_t value) {
|
||||||
|
if (*offset > 372U - 8U) return false;
|
||||||
|
for (unsigned int byte = 0; byte < 8; ++byte)
|
||||||
|
record[(*offset)++] = static_cast<unsigned char>(value >> (byte * 8U));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool put_f64(unsigned char *record, size_t *offset, double value) {
|
||||||
|
if (!std::isfinite(value)) return false;
|
||||||
|
if (value == 0.0) value = 0.0;
|
||||||
|
uint64_t bits = 0;
|
||||||
|
static_assert(sizeof(bits) == sizeof(value));
|
||||||
|
std::memcpy(&bits, &value, sizeof(bits));
|
||||||
|
return put_u64(record, offset, bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checked_add(uint64_t left, uint64_t right, uint64_t *result) {
|
||||||
|
if (right > std::numeric_limits<uint64_t>::max() - left) return false;
|
||||||
|
*result = left + right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checked_mul(uint64_t left, uint64_t right, uint64_t *result) {
|
||||||
|
if (left != 0 && right > std::numeric_limits<uint64_t>::max() / left) return false;
|
||||||
|
*result = left * right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
bool lardon3d_sparse_sfm_fingerprint_record(
|
||||||
|
const Lardon3DSparseIncrementalParameters *p, unsigned char record[372]) {
|
||||||
|
if (!p || !record) return false;
|
||||||
|
std::memset(record, 0, 372);
|
||||||
|
std::memcpy(record, "L3DSFMFP", 8);
|
||||||
|
size_t offset = 8;
|
||||||
|
bool ok = put_u32(record, &offset, 1) &&
|
||||||
|
put_u32(record, &offset, p->minimum_seed_tracks) &&
|
||||||
|
put_u32(record, &offset, p->minimum_seed_landmarks) &&
|
||||||
|
put_u32(record, &offset, p->minimum_pnp_correspondences) &&
|
||||||
|
put_u32(record, &offset, p->maximum_seed_candidates) &&
|
||||||
|
put_u32(record, &offset, p->maximum_registration_rounds) &&
|
||||||
|
put_u32(record, &offset, p->maximum_landmarks_per_round) &&
|
||||||
|
put_u32(record, &offset, p->maximum_images) &&
|
||||||
|
put_u64(record, &offset, p->maximum_observations) &&
|
||||||
|
put_u64(record, &offset, p->maximum_tracks) &&
|
||||||
|
put_f64(record, &offset, p->reprojection_threshold_px) &&
|
||||||
|
put_f64(record, &offset, p->minimum_track_parallax_rad) &&
|
||||||
|
put_f64(record, &offset, p->relative_pose.robust_threshold_px) &&
|
||||||
|
put_f64(record, &offset, p->relative_pose.confidence) &&
|
||||||
|
put_u32(record, &offset, p->relative_pose.max_iterations) &&
|
||||||
|
put_u32(record, &offset, p->relative_pose.minimum_inliers) &&
|
||||||
|
put_f64(record, &offset, p->relative_pose.minimum_inlier_ratio) &&
|
||||||
|
put_f64(record, &offset, p->relative_pose.minimum_parallax_rad) &&
|
||||||
|
put_f64(record, &offset, p->relative_pose.minimum_cheirality_ratio) &&
|
||||||
|
put_u64(record, &offset, p->relative_pose.deterministic_seed) &&
|
||||||
|
put_f64(record, &offset, p->pnp.reprojection_threshold_px) &&
|
||||||
|
put_f64(record, &offset, p->pnp.confidence) &&
|
||||||
|
put_u32(record, &offset, p->pnp.max_iterations) &&
|
||||||
|
put_u32(record, &offset, p->pnp.minimum_inliers) &&
|
||||||
|
put_f64(record, &offset, p->pnp.minimum_inlier_ratio) &&
|
||||||
|
put_u64(record, &offset, p->pnp.deterministic_seed) &&
|
||||||
|
put_u32(record, &offset, p->refinement.max_iterations) &&
|
||||||
|
put_f64(record, &offset, p->refinement.convergence_tolerance);
|
||||||
|
for (unsigned int policy = 0; ok && policy < 19; ++policy)
|
||||||
|
ok = put_u32(record, &offset, 1);
|
||||||
|
ok = ok && put_f64(record, &offset, 1e-9);
|
||||||
|
for (unsigned int policy = 0; ok && policy < 2; ++policy)
|
||||||
|
ok = put_u32(record, &offset, 1);
|
||||||
|
ok = ok && put_f64(record, &offset, 1e-9) && put_u32(record, &offset, 1) &&
|
||||||
|
put_f64(record, &offset, 2.0);
|
||||||
|
for (unsigned int policy = 0; ok && policy < 6; ++policy)
|
||||||
|
ok = put_u32(record, &offset, 1);
|
||||||
|
ok = ok && put_u32(record, &offset, 1) && put_u32(record, &offset, 50) &&
|
||||||
|
put_f64(record, &offset, 1e-6) && put_f64(record, &offset, 1e-10) &&
|
||||||
|
put_f64(record, &offset, 1e-8) && put_u32(record, &offset, 0) &&
|
||||||
|
put_u32(record, &offset, 1) && put_f64(record, &offset, 1e-12) &&
|
||||||
|
put_u32(record, &offset, 1) && put_u32(record, &offset, 1);
|
||||||
|
return ok && offset == 372;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lardon3d_sparse_sfm_parameter_fingerprint(
|
||||||
|
const Lardon3DSparseIncrementalParameters *parameters, unsigned char digest[32]) {
|
||||||
|
if (!digest) return false;
|
||||||
|
unsigned char record[372];
|
||||||
|
unsigned int length = 0;
|
||||||
|
return lardon3d_sparse_sfm_fingerprint_record(parameters, record) &&
|
||||||
|
EVP_Digest(record, sizeof(record), digest, &length, EVP_sha256(), nullptr) == 1 &&
|
||||||
|
length == 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lardon3d_sparse_sfm_resource_estimate(uint64_t image_count, uint64_t track_count,
|
||||||
|
uint64_t observation_count,
|
||||||
|
Lardon3DResourceEstimate *estimate) {
|
||||||
|
if (!estimate) return false;
|
||||||
|
uint64_t images = 0, tracks = 0, observations = 0, raw = 134217728ULL;
|
||||||
|
if (!checked_mul(image_count, 65536ULL, &images) ||
|
||||||
|
!checked_mul(track_count, 2048ULL, &tracks) ||
|
||||||
|
!checked_mul(observation_count, 512ULL, &observations) ||
|
||||||
|
!checked_add(raw, images, &raw) || !checked_add(raw, tracks, &raw) ||
|
||||||
|
!checked_add(raw, observations, &raw)) return false;
|
||||||
|
constexpr uint64_t mib = 1024ULL * 1024ULL;
|
||||||
|
uint64_t remainder = raw % mib;
|
||||||
|
if (remainder != 0 && !checked_add(raw, mib - remainder, &raw)) return false;
|
||||||
|
*estimate = {raw, 0, 0, 0, 1, 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_CPU};
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lardon3d_sparse_sfm_component_persistable(uint64_t registered_image_count,
|
||||||
|
uint64_t landmark_count) {
|
||||||
|
return registered_image_count > 0 && landmark_count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool lardon3d_sparse_sfm_publication_metrics(const double *squared_errors, size_t count,
|
||||||
|
double *rmse, double *median) {
|
||||||
|
if (!squared_errors || count == 0 || !rmse || !median) return false;
|
||||||
|
try {
|
||||||
|
std::vector<double> errors;
|
||||||
|
errors.reserve(count);
|
||||||
|
double sum = 0.0;
|
||||||
|
for (size_t index = 0; index < count; ++index) {
|
||||||
|
double squared = squared_errors[index];
|
||||||
|
if (!std::isfinite(squared) || squared < 0.0 || !std::isfinite(sum + squared)) return false;
|
||||||
|
sum += squared;
|
||||||
|
errors.push_back(std::sqrt(squared));
|
||||||
|
}
|
||||||
|
std::sort(errors.begin(), errors.end());
|
||||||
|
*rmse = std::sqrt(sum / static_cast<double>(count));
|
||||||
|
size_t middle = count / 2;
|
||||||
|
*median = count % 2 ? errors[middle]
|
||||||
|
: errors[middle - 1] + (errors[middle] - errors[middle - 1]) / 2.0;
|
||||||
|
return std::isfinite(*rmse) && std::isfinite(*median);
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/sparse_sfm_gate_f_internal.h
Normal file
23
src/sparse_sfm_gate_f_internal.h
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#ifndef LARDON3D_SPARSE_SFM_GATE_F_INTERNAL_H
|
||||||
|
#define LARDON3D_SPARSE_SFM_GATE_F_INTERNAL_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <lardon3d/resource_governor.h>
|
||||||
|
#include <lardon3d/sparse_sfm_incremental.h>
|
||||||
|
|
||||||
|
bool lardon3d_sparse_sfm_fingerprint_record(
|
||||||
|
const Lardon3DSparseIncrementalParameters *parameters, unsigned char record[372]);
|
||||||
|
bool lardon3d_sparse_sfm_parameter_fingerprint(
|
||||||
|
const Lardon3DSparseIncrementalParameters *parameters, unsigned char digest[32]);
|
||||||
|
bool lardon3d_sparse_sfm_resource_estimate(uint64_t participating_image_count,
|
||||||
|
uint64_t track_count, uint64_t observation_count,
|
||||||
|
Lardon3DResourceEstimate *estimate);
|
||||||
|
bool lardon3d_sparse_sfm_component_persistable(uint64_t registered_image_count,
|
||||||
|
uint64_t landmark_count);
|
||||||
|
bool lardon3d_sparse_sfm_publication_metrics(const double *squared_errors, size_t count,
|
||||||
|
double *rmse, double *median);
|
||||||
|
|
||||||
|
#endif
|
||||||
667
src/sparse_sfm_task.cpp
Normal file
667
src/sparse_sfm_task.cpp
Normal file
|
|
@ -0,0 +1,667 @@
|
||||||
|
#include "sparse_sfm_gate_f_internal.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <map>
|
||||||
|
#include <new>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/feature_store.h>
|
||||||
|
#include <lardon3d/project.h>
|
||||||
|
#include <lardon3d/sparse_sfm_bundle_adjustment.h>
|
||||||
|
#include <lardon3d/sparse_sfm_model.h>
|
||||||
|
#include <lardon3d/sparse_sfm_task.h>
|
||||||
|
#include <lardon3d/task_checkpoint.h>
|
||||||
|
#include <lardon3d/task_queue.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
struct Context {
|
||||||
|
std::string project_path;
|
||||||
|
Lardon3DProjectDb *database = nullptr;
|
||||||
|
Lardon3DProjectDbSparseSfmTask durable{};
|
||||||
|
struct WorkloadShape {
|
||||||
|
uint64_t image_count = 0;
|
||||||
|
uint64_t track_count = 0;
|
||||||
|
uint64_t observation_count = 0;
|
||||||
|
} shape;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ObservationKey {
|
||||||
|
uint64_t feature_set_id;
|
||||||
|
uint32_t feature_index;
|
||||||
|
|
||||||
|
bool operator<(const ObservationKey &other) const {
|
||||||
|
return std::tie(feature_set_id, feature_index) <
|
||||||
|
std::tie(other.feature_set_id, other.feature_index);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Reader {
|
||||||
|
Lardon3DProjectDbFeatureSet feature_set{};
|
||||||
|
Lardon3DFeatureReader *reader = nullptr;
|
||||||
|
Lardon3DFeatureFileMetadata metadata{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Resolved {
|
||||||
|
std::vector<Lardon3DSparseIncrementalImage> images;
|
||||||
|
std::vector<Lardon3DSparseIncrementalObservation> observations;
|
||||||
|
std::map<uint64_t, Lardon3DSparseGeometryCalibration> calibrations;
|
||||||
|
std::map<ObservationKey, size_t> observation_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PublicationStorage {
|
||||||
|
std::vector<Lardon3DSparseComponent> components;
|
||||||
|
std::vector<Lardon3DSparseRegisteredImage> cameras;
|
||||||
|
std::vector<Lardon3DSparseLandmark> landmarks;
|
||||||
|
std::vector<Lardon3DSparseLandmarkObservation> observations;
|
||||||
|
double rmse = 0.0;
|
||||||
|
double median = 0.0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void destroy_context(void *userdata) { delete static_cast<Context *>(userdata); }
|
||||||
|
|
||||||
|
bool checked_add(uint64_t left, uint64_t right, uint64_t *output) {
|
||||||
|
if (right > UINT64_MAX - left) return false;
|
||||||
|
*output = left + right;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_calibration_membership(const Context &context, std::set<uint64_t> *images) {
|
||||||
|
Lardon3DSparseCalibrationScope scope{};
|
||||||
|
if (lardon3d_sparse_calibration_scope_load(context.database,
|
||||||
|
context.durable.calibration_scope_id, &scope) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
uint64_t after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseCalibrationMember members[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
size_t count = 0;
|
||||||
|
uint64_t next = 0;
|
||||||
|
if (lardon3d_sparse_calibration_scope_list_members(
|
||||||
|
context.database, scope.scope_id, after, members, LARDON3D_SPARSE_SFM_PAGE_MAX,
|
||||||
|
&count, &next) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < count; ++index)
|
||||||
|
if (!images->insert(members[index].image_id).second) return false;
|
||||||
|
if (count == 0) break;
|
||||||
|
if (next <= after) return false;
|
||||||
|
after = next;
|
||||||
|
}
|
||||||
|
return images->size() == scope.member_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool derive_workload_shape(const Context &context, Context::WorkloadShape *shape) {
|
||||||
|
Lardon3DProjectDbTrackSet track_set{};
|
||||||
|
if (!shape ||
|
||||||
|
lardon3d_project_db_load_track_set(context.database, context.durable.track_set_id,
|
||||||
|
&track_set) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
std::set<uint64_t> calibration_images;
|
||||||
|
if (!load_calibration_membership(context, &calibration_images)) return false;
|
||||||
|
std::map<uint64_t, Lardon3DProjectDbFeatureSet> feature_sets;
|
||||||
|
std::set<uint64_t> participating_images;
|
||||||
|
Context::WorkloadShape result{};
|
||||||
|
uint64_t after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DProjectDbTrack tracks[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
size_t count = 0;
|
||||||
|
if (lardon3d_project_db_list_tracks(context.database, track_set.track_set_id, after, tracks,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &count) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK)
|
||||||
|
return false;
|
||||||
|
bool ok = true;
|
||||||
|
for (size_t index = 0; ok && index < count; ++index) {
|
||||||
|
const auto &track = tracks[index];
|
||||||
|
if (track.track_id <= after || track.track_set_id != track_set.track_set_id ||
|
||||||
|
!checked_add(result.observation_count, track.observation_count,
|
||||||
|
&result.observation_count)) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (uint32_t position = 0; position < track.observation_count; ++position) {
|
||||||
|
const auto &observation = track.observations[position];
|
||||||
|
auto feature = feature_sets.find(observation.feature_set_id);
|
||||||
|
if (feature == feature_sets.end()) {
|
||||||
|
Lardon3DProjectDbFeatureSet feature_set{};
|
||||||
|
if (lardon3d_project_db_load_feature_set(context.database,
|
||||||
|
observation.feature_set_id,
|
||||||
|
&feature_set) != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
feature = feature_sets.emplace(observation.feature_set_id, feature_set).first;
|
||||||
|
}
|
||||||
|
if (observation.feature_index >= feature->second.feature_count ||
|
||||||
|
!calibration_images.count(feature->second.image_id)) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
participating_images.insert(feature->second.image_id);
|
||||||
|
}
|
||||||
|
after = track.track_id;
|
||||||
|
++result.track_count;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < count; ++index)
|
||||||
|
lardon3d_project_db_free_track(&tracks[index]);
|
||||||
|
if (!ok) return false;
|
||||||
|
if (count == 0) break;
|
||||||
|
}
|
||||||
|
result.image_count = participating_images.size();
|
||||||
|
if (result.track_count != track_set.track_count || result.image_count == 0 ||
|
||||||
|
result.observation_count == 0)
|
||||||
|
return false;
|
||||||
|
*shape = result;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_observation_index(Resolved *resolved) {
|
||||||
|
resolved->observation_index.clear();
|
||||||
|
for (size_t index = 0; index < resolved->observations.size(); ++index) {
|
||||||
|
const auto &observation = resolved->observations[index];
|
||||||
|
if (!resolved->observation_index
|
||||||
|
.emplace(ObservationKey{observation.feature_set_id, observation.feature_index}, index)
|
||||||
|
.second)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void project_landmarks(const Lardon3DSparseIncrementalLandmark *landmarks,
|
||||||
|
size_t landmark_count, const std::set<uint64_t> &component_keys,
|
||||||
|
std::vector<Lardon3DSparseLandmark> *output) {
|
||||||
|
for (size_t index = 0; index < landmark_count; ++index) {
|
||||||
|
const auto &landmark = landmarks[index];
|
||||||
|
if (!component_keys.count(landmark.component_key)) continue;
|
||||||
|
output->push_back(
|
||||||
|
{0, landmark.track_id, landmark.component_key, landmark.point.x, landmark.point.y,
|
||||||
|
landmark.point.z, landmark.reprojection_rmse_px, landmark.reprojection_median_px,
|
||||||
|
landmark.observation_count});
|
||||||
|
}
|
||||||
|
std::sort(output->begin(), output->end(), [](const auto &a, const auto &b) {
|
||||||
|
return std::tie(a.component_key, a.track_id) <
|
||||||
|
std::tie(b.component_key, b.track_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void close_readers(std::map<uint64_t, Reader> *readers) {
|
||||||
|
for (auto &item : *readers)
|
||||||
|
if (item.second.reader) lardon3d_feature_reader_close(item.second.reader);
|
||||||
|
readers->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_reader(const Context &context, uint64_t feature_set_id,
|
||||||
|
std::map<uint64_t, Reader> *readers, Reader **output) {
|
||||||
|
auto existing = readers->find(feature_set_id);
|
||||||
|
if (existing != readers->end()) {
|
||||||
|
*output = &existing->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Reader candidate;
|
||||||
|
if (lardon3d_project_db_load_feature_set(context.database, feature_set_id,
|
||||||
|
&candidate.feature_set) != LARDON3D_PROJECT_DB_OK ||
|
||||||
|
lardon3d_feature_reader_open(context.project_path.c_str(), &candidate.feature_set,
|
||||||
|
&candidate.reader, &candidate.metadata) !=
|
||||||
|
LARDON3D_FEATURE_STORE_OK ||
|
||||||
|
candidate.metadata.feature_count != candidate.feature_set.feature_count) {
|
||||||
|
if (candidate.reader) lardon3d_feature_reader_close(candidate.reader);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto inserted = readers->emplace(feature_set_id, candidate);
|
||||||
|
*output = &inserted.first->second;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_calibrations(const Context &context, Resolved *resolved) {
|
||||||
|
Lardon3DSparseCalibrationScope scope{};
|
||||||
|
if (lardon3d_sparse_calibration_scope_load(context.database,
|
||||||
|
context.durable.calibration_scope_id, &scope) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK) return false;
|
||||||
|
uint64_t after = 0;
|
||||||
|
for (;;) {
|
||||||
|
Lardon3DSparseCalibrationMember members[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
size_t count = 0;
|
||||||
|
uint64_t next = 0;
|
||||||
|
if (lardon3d_sparse_calibration_scope_list_members(
|
||||||
|
context.database, scope.scope_id, after, members, LARDON3D_SPARSE_SFM_PAGE_MAX,
|
||||||
|
&count, &next) != LARDON3D_PROJECT_DB_OK) return false;
|
||||||
|
for (size_t index = 0; index < count; ++index) {
|
||||||
|
Lardon3DSparseCalibration calibration{};
|
||||||
|
if (lardon3d_sparse_calibration_load(context.database, members[index].calibration_id,
|
||||||
|
&calibration) != LARDON3D_PROJECT_DB_OK ||
|
||||||
|
std::memcmp(calibration.scientific_hash, members[index].calibration_hash, 32) != 0)
|
||||||
|
return false;
|
||||||
|
resolved->calibrations.emplace(
|
||||||
|
members[index].image_id,
|
||||||
|
Lardon3DSparseGeometryCalibration{calibration.width, calibration.height, calibration.fx,
|
||||||
|
calibration.fy, calibration.cx, calibration.cy,
|
||||||
|
calibration.k1, calibration.k2, calibration.p1,
|
||||||
|
calibration.p2});
|
||||||
|
}
|
||||||
|
if (count == 0) break;
|
||||||
|
if (next <= after) return false;
|
||||||
|
after = next;
|
||||||
|
}
|
||||||
|
return resolved->calibrations.size() == scope.member_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool resolve_input(const Context &context, Resolved *resolved, uint64_t *track_count) {
|
||||||
|
Lardon3DProjectDbTrackSet track_set{};
|
||||||
|
if (lardon3d_project_db_load_track_set(context.database, context.durable.track_set_id,
|
||||||
|
&track_set) != LARDON3D_PROJECT_DB_OK ||
|
||||||
|
!load_calibrations(context, resolved)) return false;
|
||||||
|
std::map<uint64_t, Reader> readers;
|
||||||
|
std::set<uint64_t> participating_images;
|
||||||
|
uint64_t after = 0;
|
||||||
|
uint64_t tracks_seen = 0;
|
||||||
|
bool ok = true;
|
||||||
|
while (ok) {
|
||||||
|
Lardon3DProjectDbTrack tracks[LARDON3D_SPARSE_SFM_PAGE_MAX]{};
|
||||||
|
size_t count = 0;
|
||||||
|
if (lardon3d_project_db_list_tracks(context.database, track_set.track_set_id, after, tracks,
|
||||||
|
LARDON3D_SPARSE_SFM_PAGE_MAX, &count) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; ok && index < count; ++index) {
|
||||||
|
const Lardon3DProjectDbTrack &track = tracks[index];
|
||||||
|
if (track.track_id <= after || track.track_set_id != track_set.track_set_id) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (uint32_t position = 0; ok && position < track.observation_count; ++position) {
|
||||||
|
const Lardon3DProjectDbTrackObservation &observation = track.observations[position];
|
||||||
|
Reader *reader = nullptr;
|
||||||
|
Lardon3DFeatureKeypoint keypoint{};
|
||||||
|
if (!load_reader(context, observation.feature_set_id, &readers, &reader) ||
|
||||||
|
observation.feature_index >= reader->metadata.feature_count ||
|
||||||
|
lardon3d_feature_reader_keypoints(reader->reader, observation.feature_index, &keypoint,
|
||||||
|
1) != LARDON3D_FEATURE_STORE_OK ||
|
||||||
|
!std::isfinite(keypoint.x) || !std::isfinite(keypoint.y) ||
|
||||||
|
resolved->calibrations.find(reader->feature_set.image_id) ==
|
||||||
|
resolved->calibrations.end()) {
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
participating_images.insert(reader->feature_set.image_id);
|
||||||
|
resolved->observations.push_back(
|
||||||
|
{track.track_id, reader->feature_set.image_id, observation.feature_set_id,
|
||||||
|
observation.feature_index, reader->metadata.feature_count,
|
||||||
|
static_cast<double>(keypoint.x), static_cast<double>(keypoint.y)});
|
||||||
|
}
|
||||||
|
after = track.track_id;
|
||||||
|
++tracks_seen;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < count; ++index)
|
||||||
|
lardon3d_project_db_free_track(&tracks[index]);
|
||||||
|
if (!ok || count == 0) break;
|
||||||
|
}
|
||||||
|
close_readers(&readers);
|
||||||
|
if (!ok || tracks_seen != track_set.track_count) return false;
|
||||||
|
for (uint64_t image_id : participating_images)
|
||||||
|
resolved->images.push_back({image_id, resolved->calibrations.at(image_id)});
|
||||||
|
*track_count = tracks_seen;
|
||||||
|
return !resolved->images.empty() && !resolved->observations.empty() &&
|
||||||
|
build_observation_index(resolved);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool persist(Context *context, const Lardon3DTask *task) {
|
||||||
|
Lardon3DTaskDurableSnapshot snapshot{};
|
||||||
|
if (!lardon3d_task_durable_snapshot(task, &snapshot)) return false;
|
||||||
|
Lardon3DProjectDbCheckpoint checkpoint{};
|
||||||
|
std::snprintf(checkpoint.path, sizeof(checkpoint.path), ".lardon3d/checkpoints/%llu.chk",
|
||||||
|
static_cast<unsigned long long>(snapshot.id));
|
||||||
|
checkpoint.format_version = LARDON3D_TASK_CHECKPOINT_VERSION;
|
||||||
|
checkpoint.updated_at = 0;
|
||||||
|
std::string path = context->project_path + "/" + checkpoint.path;
|
||||||
|
Lardon3DTaskCheckpointResult saved = lardon3d_task_checkpoint_save(path.c_str(), &snapshot);
|
||||||
|
if (saved != LARDON3D_TASK_CHECKPOINT_OK &&
|
||||||
|
saved != LARDON3D_TASK_CHECKPOINT_PUBLISHED_NOT_DURABLE) return false;
|
||||||
|
checkpoint.durability = saved == LARDON3D_TASK_CHECKPOINT_OK
|
||||||
|
? LARDON3D_DB_CHECKPOINT_DURABLE
|
||||||
|
: LARDON3D_DB_CHECKPOINT_PUBLISHED_NOT_DURABLE;
|
||||||
|
return lardon3d_project_db_record_sparse_sfm_task(
|
||||||
|
context->database, &snapshot, LARDON3D_SPARSE_SFM_TASK_KIND,
|
||||||
|
LARDON3D_SPARSE_SFM_TASK_KIND_VERSION, &checkpoint, &context->durable, 0) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Lardon3DSparseIncrementalObservation *find_input_observation(
|
||||||
|
const Resolved &resolved, uint64_t track_id, uint64_t image_id, uint64_t feature_set_id,
|
||||||
|
uint32_t feature_index) {
|
||||||
|
auto found = resolved.observation_index.find(ObservationKey{feature_set_id, feature_index});
|
||||||
|
if (found == resolved.observation_index.end()) return nullptr;
|
||||||
|
const auto &item = resolved.observations[found->second];
|
||||||
|
return item.track_id == track_id && item.image_id == image_id ? &item : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_publication(const Resolved &resolved,
|
||||||
|
const Lardon3DSparseBundleAdjustmentResult &result,
|
||||||
|
PublicationStorage *storage) {
|
||||||
|
std::set<uint64_t> component_keys;
|
||||||
|
for (size_t index = 0; index < result.component_count; ++index) {
|
||||||
|
const auto &component = result.components[index];
|
||||||
|
if (lardon3d_sparse_sfm_component_persistable(component.registered_image_count,
|
||||||
|
component.landmark_count)) {
|
||||||
|
component_keys.insert(component.component_key);
|
||||||
|
storage->components.push_back({0, component.component_key, component.registered_image_count,
|
||||||
|
component.landmark_count});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (storage->components.empty()) return false;
|
||||||
|
for (size_t index = 0; index < result.camera_count; ++index) {
|
||||||
|
const auto &camera = result.cameras[index];
|
||||||
|
if (!component_keys.count(camera.component_key)) continue;
|
||||||
|
Lardon3DSparseRegisteredImage output{};
|
||||||
|
output.image_id = camera.image_id;
|
||||||
|
output.component_key = camera.component_key;
|
||||||
|
std::memcpy(output.rotation_cw, camera.pose_cw.rotation_cw, sizeof(output.rotation_cw));
|
||||||
|
std::memcpy(output.translation_cw, camera.pose_cw.translation_cw,
|
||||||
|
sizeof(output.translation_cw));
|
||||||
|
storage->cameras.push_back(output);
|
||||||
|
}
|
||||||
|
project_landmarks(result.landmarks, result.landmark_count, component_keys,
|
||||||
|
&storage->landmarks);
|
||||||
|
std::sort(storage->cameras.begin(), storage->cameras.end(),
|
||||||
|
[](const auto &a, const auto &b) { return a.image_id < b.image_id; });
|
||||||
|
std::map<uint64_t, const Lardon3DSparseRegisteredImage *> cameras;
|
||||||
|
for (const auto &camera : storage->cameras) cameras.emplace(camera.image_id, &camera);
|
||||||
|
std::map<uint64_t, const Lardon3DSparseLandmark *> landmarks;
|
||||||
|
for (const auto &landmark : storage->landmarks) landmarks.emplace(landmark.track_id, &landmark);
|
||||||
|
std::vector<double> squared_errors;
|
||||||
|
for (size_t index = 0; index < result.observation_count; ++index) {
|
||||||
|
const auto &observation = result.observations[index];
|
||||||
|
auto landmark = landmarks.find(observation.track_id);
|
||||||
|
if (landmark == landmarks.end()) continue;
|
||||||
|
auto camera = cameras.find(observation.image_id);
|
||||||
|
auto calibration = resolved.calibrations.find(observation.image_id);
|
||||||
|
const auto *source = find_input_observation(resolved, observation.track_id,
|
||||||
|
observation.image_id,
|
||||||
|
observation.feature_set_id,
|
||||||
|
observation.feature_index);
|
||||||
|
if (camera == cameras.end() || calibration == resolved.calibrations.end() || !source)
|
||||||
|
return false;
|
||||||
|
const auto &pose = *camera->second;
|
||||||
|
const auto &point = *landmark->second;
|
||||||
|
double xc = pose.rotation_cw[0] * point.x + pose.rotation_cw[1] * point.y +
|
||||||
|
pose.rotation_cw[2] * point.z + pose.translation_cw[0];
|
||||||
|
double yc = pose.rotation_cw[3] * point.x + pose.rotation_cw[4] * point.y +
|
||||||
|
pose.rotation_cw[5] * point.z + pose.translation_cw[1];
|
||||||
|
double zc = pose.rotation_cw[6] * point.x + pose.rotation_cw[7] * point.y +
|
||||||
|
pose.rotation_cw[8] * point.z + pose.translation_cw[2];
|
||||||
|
if (!std::isfinite(xc) || !std::isfinite(yc) || !std::isfinite(zc) || zc <= 1e-9)
|
||||||
|
return false;
|
||||||
|
double xn = xc / zc, yn = yc / zc, r2 = xn * xn + yn * yn;
|
||||||
|
const auto &cal = calibration->second;
|
||||||
|
double radial = 1.0 + cal.k1 * r2 + cal.k2 * r2 * r2;
|
||||||
|
double xd = xn * radial + 2.0 * cal.p1 * xn * yn + cal.p2 * (r2 + 2.0 * xn * xn);
|
||||||
|
double yd = yn * radial + cal.p1 * (r2 + 2.0 * yn * yn) + 2.0 * cal.p2 * xn * yn;
|
||||||
|
double dx = cal.fx * xd + cal.cx - source->x;
|
||||||
|
double dy = cal.fy * yd + cal.cy - source->y;
|
||||||
|
double squared = dx * dx + dy * dy;
|
||||||
|
if (!std::isfinite(squared)) return false;
|
||||||
|
squared_errors.push_back(squared);
|
||||||
|
storage->observations.push_back(
|
||||||
|
{0, observation.track_id, observation.feature_set_id, observation.feature_index,
|
||||||
|
observation.position_in_track});
|
||||||
|
}
|
||||||
|
if (storage->cameras.size() < 2 || storage->landmarks.empty() || squared_errors.empty())
|
||||||
|
return false;
|
||||||
|
std::sort(storage->observations.begin(), storage->observations.end(), [](const auto &a,
|
||||||
|
const auto &b) {
|
||||||
|
return std::tie(a.track_id, a.position_in_track) <
|
||||||
|
std::tie(b.track_id, b.position_in_track);
|
||||||
|
});
|
||||||
|
return lardon3d_sparse_sfm_publication_metrics(squared_errors.data(), squared_errors.size(),
|
||||||
|
&storage->rmse, &storage->median);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool execute(Context *context, Lardon3DTask *task, bool *reused) {
|
||||||
|
unsigned char fingerprint[32]{};
|
||||||
|
if (!lardon3d_sparse_sfm_parameter_fingerprint(&context->durable.parameters, fingerprint))
|
||||||
|
return false;
|
||||||
|
Lardon3DSparseReconstruction existing{};
|
||||||
|
Lardon3DProjectDbResult lookup = lardon3d_sparse_reconstruction_find_exact(
|
||||||
|
context->database, context->durable.track_set_id, context->durable.calibration_scope_id,
|
||||||
|
context->durable.sfm_kind, context->durable.sfm_version, fingerprint, &existing);
|
||||||
|
if (lookup == LARDON3D_PROJECT_DB_OK) {
|
||||||
|
*reused = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (lookup != LARDON3D_PROJECT_DB_NOT_FOUND || !lardon3d_task_checkpoint(task)) return false;
|
||||||
|
Resolved resolved;
|
||||||
|
uint64_t track_count = 0;
|
||||||
|
if (!resolve_input(*context, &resolved, &track_count)) return false;
|
||||||
|
if (track_count != context->shape.track_count ||
|
||||||
|
resolved.observations.size() != context->shape.observation_count ||
|
||||||
|
resolved.images.size() != context->shape.image_count)
|
||||||
|
return false;
|
||||||
|
Lardon3DSparseIncrementalInput input{context->durable.track_set_id,
|
||||||
|
context->durable.calibration_scope_id,
|
||||||
|
resolved.images.data(), resolved.images.size(),
|
||||||
|
resolved.observations.data(), resolved.observations.size()};
|
||||||
|
Lardon3DSparseIncrementalResult incremental{};
|
||||||
|
Lardon3DSparseIncrementalStatus d_status = lardon3d_sparse_incremental_run(
|
||||||
|
&input, &context->durable.parameters, &incremental);
|
||||||
|
if (d_status != LARDON3D_SPARSE_INCREMENTAL_COMPLETE &&
|
||||||
|
d_status != LARDON3D_SPARSE_INCREMENTAL_PARTIAL) {
|
||||||
|
lardon3d_sparse_incremental_result_destroy(&incremental);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!lardon3d_task_checkpoint(task)) {
|
||||||
|
lardon3d_sparse_incremental_result_destroy(&incremental);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Lardon3DSparseBundleAdjustmentInput e_input{&incremental, resolved.images.data(),
|
||||||
|
resolved.images.size(),
|
||||||
|
resolved.observations.data(),
|
||||||
|
resolved.observations.size()};
|
||||||
|
Lardon3DSparseBundleAdjustmentResult adjusted{};
|
||||||
|
auto e_status = lardon3d_sparse_bundle_adjustment_run(&e_input, &adjusted);
|
||||||
|
lardon3d_sparse_incremental_result_destroy(&incremental);
|
||||||
|
if (e_status != LARDON3D_SPARSE_BUNDLE_ADJUSTMENT_EXECUTION_OK ||
|
||||||
|
!lardon3d_task_checkpoint(task)) {
|
||||||
|
lardon3d_sparse_bundle_adjustment_result_destroy(&adjusted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
PublicationStorage storage;
|
||||||
|
bool valid = build_publication(resolved, adjusted, &storage);
|
||||||
|
if (!valid) {
|
||||||
|
lardon3d_sparse_bundle_adjustment_result_destroy(&adjusted);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Lardon3DSparsePublication publication{
|
||||||
|
context->durable.track_set_id,
|
||||||
|
context->durable.calibration_scope_id,
|
||||||
|
context->durable.sfm_kind,
|
||||||
|
context->durable.sfm_version,
|
||||||
|
{},
|
||||||
|
storage.components.data(),
|
||||||
|
storage.components.size(),
|
||||||
|
storage.cameras.data(),
|
||||||
|
storage.cameras.size(),
|
||||||
|
storage.landmarks.data(),
|
||||||
|
storage.landmarks.size(),
|
||||||
|
storage.observations.data(),
|
||||||
|
storage.observations.size(),
|
||||||
|
storage.rmse,
|
||||||
|
storage.median,
|
||||||
|
static_cast<int64_t>(std::time(nullptr))};
|
||||||
|
std::memcpy(publication.parameter_fingerprint, fingerprint, 32);
|
||||||
|
Lardon3DSparseReconstruction published{};
|
||||||
|
Lardon3DProjectDbResult publish_result =
|
||||||
|
lardon3d_sparse_reconstruction_publish(context->database, &publication, &published);
|
||||||
|
lardon3d_sparse_bundle_adjustment_result_destroy(&adjusted);
|
||||||
|
if (publish_result == LARDON3D_PROJECT_DB_OK) return true;
|
||||||
|
if (publish_result != LARDON3D_PROJECT_DB_CONSTRAINT) return false;
|
||||||
|
lookup = lardon3d_sparse_reconstruction_find_exact(
|
||||||
|
context->database, context->durable.track_set_id, context->durable.calibration_scope_id,
|
||||||
|
context->durable.sfm_kind, context->durable.sfm_version, fingerprint, &existing);
|
||||||
|
*reused = lookup == LARDON3D_PROJECT_DB_OK;
|
||||||
|
return *reused;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool run(Lardon3DTask *task, void *userdata) {
|
||||||
|
try {
|
||||||
|
bool reused = false;
|
||||||
|
if (!execute(static_cast<Context *>(userdata), task, &reused))
|
||||||
|
return lardon3d_task_fail(task, "Sparse SfM non publiable.");
|
||||||
|
return lardon3d_task_set_progress(task, 100,
|
||||||
|
reused ? "Reconstruction réutilisée."
|
||||||
|
: "Reconstruction publiée.");
|
||||||
|
} catch (const std::bad_alloc &) {
|
||||||
|
return lardon3d_task_fail(task, "Mémoire insuffisante pour Sparse SfM.");
|
||||||
|
} catch (...) {
|
||||||
|
return lardon3d_task_fail(task, "Erreur interne Sparse SfM.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void finished(const Lardon3DTask *task, void *userdata) {
|
||||||
|
try {
|
||||||
|
(void)persist(static_cast<Context *>(userdata), task);
|
||||||
|
} catch (...) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
#ifdef LARDON3D_SPARSE_SFM_TASK_TESTING
|
||||||
|
extern "C" int lardon3d_sparse_sfm_task_test_observation_lookup(
|
||||||
|
const Lardon3DSparseIncrementalObservation *observations, size_t count,
|
||||||
|
uint64_t track_id, uint64_t image_id, uint64_t feature_set_id,
|
||||||
|
uint32_t feature_index, size_t *resolved_index) {
|
||||||
|
try {
|
||||||
|
if ((!observations && count != 0) || !resolved_index) return -1;
|
||||||
|
Resolved resolved;
|
||||||
|
if (count != 0) resolved.observations.assign(observations, observations + count);
|
||||||
|
if (!build_observation_index(&resolved)) return -1;
|
||||||
|
const auto *found = find_input_observation(resolved, track_id, image_id,
|
||||||
|
feature_set_id, feature_index);
|
||||||
|
if (!found) return 0;
|
||||||
|
*resolved_index = static_cast<size_t>(found - resolved.observations.data());
|
||||||
|
return 1;
|
||||||
|
} catch (...) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_sparse_sfm_task_test_project_landmarks(
|
||||||
|
const Lardon3DSparseIncrementalLandmark *landmarks, size_t landmark_count,
|
||||||
|
const uint64_t *component_keys, size_t component_count,
|
||||||
|
Lardon3DSparseLandmark *output, size_t output_capacity, size_t *output_count) {
|
||||||
|
try {
|
||||||
|
if ((!landmarks && landmark_count != 0) ||
|
||||||
|
(!component_keys && component_count != 0) || !output_count ||
|
||||||
|
(!output && output_capacity != 0))
|
||||||
|
return false;
|
||||||
|
std::set<uint64_t> persistable_components;
|
||||||
|
for (size_t index = 0; index < component_count; ++index)
|
||||||
|
if (!persistable_components.insert(component_keys[index]).second) return false;
|
||||||
|
std::vector<Lardon3DSparseLandmark> projected;
|
||||||
|
project_landmarks(landmarks, landmark_count, persistable_components, &projected);
|
||||||
|
if (projected.size() > output_capacity || (!output && !projected.empty())) return false;
|
||||||
|
std::copy(projected.begin(), projected.end(), output);
|
||||||
|
*output_count = projected.size();
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_sparse_sfm_task_reconstruct(
|
||||||
|
const Lardon3DTaskDurableSnapshot *snapshot, void *userdata,
|
||||||
|
Lardon3DTaskKindBinding *binding) {
|
||||||
|
try {
|
||||||
|
if (!snapshot || !userdata || !binding) return false;
|
||||||
|
auto *runtime = static_cast<Lardon3DTaskReconstructionContext *>(userdata);
|
||||||
|
Lardon3DProjectDbSparseSfmTask durable{};
|
||||||
|
if (lardon3d_project_db_load_sparse_sfm_task(runtime->project_db, snapshot->id, &durable) !=
|
||||||
|
LARDON3D_PROJECT_DB_OK) return false;
|
||||||
|
auto *context = new (std::nothrow) Context;
|
||||||
|
if (!context) return false;
|
||||||
|
context->project_path = runtime->project_path;
|
||||||
|
context->database = runtime->project_db;
|
||||||
|
context->durable = durable;
|
||||||
|
unsigned char fingerprint[32];
|
||||||
|
if (!lardon3d_sparse_sfm_parameter_fingerprint(&durable.parameters, fingerprint) ||
|
||||||
|
!derive_workload_shape(*context, &context->shape)) {
|
||||||
|
delete context;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*binding = {run, context, destroy_context, finished, context};
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" Lardon3DTask *lardon3d_project_create_sparse_sfm_task(
|
||||||
|
Lardon3DAppState *state, const Lardon3DSparseSfmTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id) {
|
||||||
|
try {
|
||||||
|
if (task_id) *task_id = 0;
|
||||||
|
if (!state || !state->project_loaded || !state->project_db || !state->resource_governor ||
|
||||||
|
!configuration)
|
||||||
|
return nullptr;
|
||||||
|
Context temporary;
|
||||||
|
temporary.project_path = state->project_path;
|
||||||
|
temporary.database = state->project_db;
|
||||||
|
temporary.durable.track_set_id = configuration->track_set_id;
|
||||||
|
temporary.durable.calibration_scope_id = configuration->calibration_scope_id;
|
||||||
|
if (!derive_workload_shape(temporary, &temporary.shape)) return nullptr;
|
||||||
|
Lardon3DResourceEstimate estimate{};
|
||||||
|
if (!lardon3d_sparse_sfm_resource_estimate(
|
||||||
|
temporary.shape.image_count, temporary.shape.track_count,
|
||||||
|
temporary.shape.observation_count, &estimate))
|
||||||
|
return nullptr;
|
||||||
|
uint64_t id = 0;
|
||||||
|
if (lardon3d_project_db_allocate_task_id(state->project_db, &id) != LARDON3D_PROJECT_DB_OK)
|
||||||
|
return nullptr;
|
||||||
|
auto *context = new (std::nothrow) Context;
|
||||||
|
if (!context) return nullptr;
|
||||||
|
context->project_path = state->project_path;
|
||||||
|
context->database = state->project_db;
|
||||||
|
context->shape = temporary.shape;
|
||||||
|
context->durable = {id, configuration->track_set_id, configuration->calibration_scope_id,
|
||||||
|
LARDON3D_SPARSE_SFM_KIND_INCREMENTAL, LARDON3D_SPARSE_SFM_VERSION,
|
||||||
|
configuration->parameters};
|
||||||
|
unsigned char fingerprint[32];
|
||||||
|
if (!lardon3d_sparse_sfm_parameter_fingerprint(&context->durable.parameters, fingerprint)) {
|
||||||
|
delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Lardon3DTask *task = lardon3d_task_create_typed(
|
||||||
|
"Sparse SfM", &estimate, LARDON3D_SPARSE_SFM_TASK_KIND,
|
||||||
|
LARDON3D_SPARSE_SFM_TASK_KIND_VERSION, run, context, destroy_context);
|
||||||
|
if (!task || !lardon3d_task_assign_id(task, id) ||
|
||||||
|
!lardon3d_task_set_finished_callback(task, finished, context) || !persist(context, task)) {
|
||||||
|
if (task) lardon3d_task_destroy(task);
|
||||||
|
else delete context;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (task_id) *task_id = id;
|
||||||
|
return task;
|
||||||
|
} catch (...) {
|
||||||
|
if (task_id) *task_id = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" bool lardon3d_project_enqueue_sparse_sfm_task(
|
||||||
|
Lardon3DAppState *state, const Lardon3DSparseSfmTaskConfiguration *configuration,
|
||||||
|
uint64_t *task_id) {
|
||||||
|
Lardon3DTask *task = lardon3d_project_create_sparse_sfm_task(state, configuration, task_id);
|
||||||
|
if (!task || !lardon3d_task_queue_add(state->task_queue, task, nullptr)) {
|
||||||
|
if (task) lardon3d_task_destroy(task);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,9 @@
|
||||||
#ifdef LARDON3D_TRACK_BUILDER_TASK_AVAILABLE
|
#ifdef LARDON3D_TRACK_BUILDER_TASK_AVAILABLE
|
||||||
#include <lardon3d/track_builder_task.h>
|
#include <lardon3d/track_builder_task.h>
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef LARDON3D_SPARSE_SFM_TASK_AVAILABLE
|
||||||
|
#include <lardon3d/sparse_sfm_task.h>
|
||||||
|
#endif
|
||||||
#include <lardon3d/task_kind_registry.h>
|
#include <lardon3d/task_kind_registry.h>
|
||||||
|
|
||||||
const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||||
|
|
@ -58,6 +61,13 @@ const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||||
.kind_version = LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION,
|
.kind_version = LARDON3D_TRACK_BUILDER_TASK_KIND_VERSION,
|
||||||
.reconstruct = lardon3d_track_builder_task_reconstruct,
|
.reconstruct = lardon3d_track_builder_task_reconstruct,
|
||||||
},
|
},
|
||||||
|
#endif
|
||||||
|
#ifdef LARDON3D_SPARSE_SFM_TASK_AVAILABLE
|
||||||
|
{
|
||||||
|
.kind = LARDON3D_SPARSE_SFM_TASK_KIND,
|
||||||
|
.kind_version = LARDON3D_SPARSE_SFM_TASK_KIND_VERSION,
|
||||||
|
.reconstruct = lardon3d_sparse_sfm_task_reconstruct,
|
||||||
|
},
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
static const Lardon3DTaskKindRegistry registry = {
|
static const Lardon3DTaskKindRegistry registry = {
|
||||||
|
|
|
||||||
|
|
@ -172,6 +172,7 @@ static bool create_v11_database(const char *path) {
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
return execute_sql(path,
|
return execute_sql(path,
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
"DROP TABLE IF EXISTS sparse_registered_images;"
|
"DROP TABLE IF EXISTS sparse_registered_images;"
|
||||||
|
|
@ -194,7 +195,7 @@ static bool test_model_api(const char *path) {
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(path, &database, error) ==
|
CHECK(lardon3d_project_db_open(path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
Parents parents;
|
Parents parents;
|
||||||
CHECK(create_parents(database, &parents));
|
CHECK(create_parents(database, &parents));
|
||||||
|
|
||||||
|
|
@ -478,7 +479,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v11_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v11_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
Parents parents;
|
Parents parents;
|
||||||
CHECK(create_parents(database, &parents));
|
CHECK(create_parents(database, &parents));
|
||||||
unsigned char fingerprint[32] = {0x91};
|
unsigned char fingerprint[32] = {0x91};
|
||||||
|
|
@ -493,7 +494,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
migrated_result.geometric_verification_result_id;
|
migrated_result.geometric_verification_result_id;
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
|
v11_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v11_path,
|
query_integer(v11_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='index' AND "
|
||||||
|
|
@ -522,7 +523,7 @@ static bool test_migration(const char *v11_path, const char *failed_path) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,7 @@ static bool run_task_test(void) {
|
||||||
CHECK(exec_sql(
|
CHECK(exec_sql(
|
||||||
database_path,
|
database_path,
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
"DROP TABLE IF EXISTS sparse_registered_images;"
|
"DROP TABLE IF EXISTS sparse_registered_images;"
|
||||||
|
|
@ -361,7 +362,7 @@ static bool run_task_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
CHECK(remove_tree(root));
|
CHECK(remove_tree(root));
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ static bool create_v9_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
"DROP TABLE IF EXISTS sparse_registered_images;"
|
"DROP TABLE IF EXISTS sparse_registered_images;"
|
||||||
|
|
@ -116,7 +117,7 @@ static bool run_test(void) {
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 16);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 17);
|
||||||
|
|
||||||
Lardon3DProjectDbScanSet scanset;
|
Lardon3DProjectDbScanSet scanset;
|
||||||
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
CHECK(lardon3d_project_db_create_scanset(database, "Match-test", &scanset) ==
|
||||||
|
|
@ -493,7 +494,7 @@ static bool run_test(void) {
|
||||||
|
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
|
|
||||||
/* Verify persistence: load previously created results */
|
/* Verify persistence: load previously created results */
|
||||||
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
|
CHECK(lardon3d_project_db_load_match_result(database, first_id, &loaded) ==
|
||||||
|
|
@ -536,11 +537,11 @@ static bool run_test(void) {
|
||||||
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 9));
|
||||||
CHECK(lardon3d_project_db_open(v9_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v9_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
|
v9_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v9_path,
|
query_integer(v9_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
@ -568,7 +569,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v10_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ static bool downgrade_project_to_historical_v10(const char *database_path) {
|
||||||
}
|
}
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE IF EXISTS sparse_sfm_tasks;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
"DROP TABLE IF EXISTS sparse_landmark_observations;"
|
||||||
"DROP TABLE IF EXISTS sparse_landmarks;"
|
"DROP TABLE IF EXISTS sparse_landmarks;"
|
||||||
"DROP TABLE IF EXISTS sparse_registered_images;"
|
"DROP TABLE IF EXISTS sparse_registered_images;"
|
||||||
|
|
@ -365,7 +366,7 @@ static bool run_test(void) {
|
||||||
"name='matcher_tasks'",
|
"name='matcher_tasks'",
|
||||||
0));
|
0));
|
||||||
CHECK(reopen_runtime(&fixture));
|
CHECK(reopen_runtime(&fixture));
|
||||||
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 16);
|
CHECK(lardon3d_project_db_schema_version(fixture.state.project_db) == 17);
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(database_path,
|
query_integer(database_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ static bool create_future_database(const char *path) {
|
||||||
sqlite3_exec(
|
sqlite3_exec(
|
||||||
connection,
|
connection,
|
||||||
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||||
"INSERT INTO metadata VALUES('schema_version',17);",
|
"INSERT INTO metadata VALUES('schema_version',18);",
|
||||||
NULL, NULL, NULL) == SQLITE_OK;
|
NULL, NULL, NULL) == SQLITE_OK;
|
||||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||||
}
|
}
|
||||||
|
|
@ -133,6 +133,7 @@ static bool create_v7_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
"DROP TABLE sparse_registered_images;"
|
"DROP TABLE sparse_registered_images;"
|
||||||
|
|
@ -169,6 +170,7 @@ static bool create_v6_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
"DROP TABLE sparse_registered_images;"
|
"DROP TABLE sparse_registered_images;"
|
||||||
|
|
@ -213,6 +215,7 @@ static bool create_v10_database(const char *path) {
|
||||||
}
|
}
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
"DROP TABLE sparse_registered_images;"
|
"DROP TABLE sparse_registered_images;"
|
||||||
|
|
@ -246,6 +249,7 @@ static bool create_v13_database(const char *path) {
|
||||||
return false;
|
return false;
|
||||||
static const char sql[] =
|
static const char sql[] =
|
||||||
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
"PRAGMA foreign_keys=OFF;BEGIN IMMEDIATE;"
|
||||||
|
"DROP TABLE sparse_sfm_tasks;"
|
||||||
"DROP TABLE sparse_landmark_observations;"
|
"DROP TABLE sparse_landmark_observations;"
|
||||||
"DROP TABLE sparse_landmarks;"
|
"DROP TABLE sparse_landmarks;"
|
||||||
"DROP TABLE sparse_registered_images;"
|
"DROP TABLE sparse_registered_images;"
|
||||||
|
|
@ -914,7 +918,7 @@ static bool run_test(void) {
|
||||||
Lardon3DProjectDb *database = NULL;
|
Lardon3DProjectDb *database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(database && lardon3d_project_db_schema_version(database) == 16);
|
CHECK(database && lardon3d_project_db_schema_version(database) == 17);
|
||||||
bool legacy_pending = true;
|
bool legacy_pending = true;
|
||||||
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
|
@ -1232,7 +1236,7 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(database_path,
|
CHECK(query_integer(database_path,
|
||||||
"SELECT value FROM metadata WHERE key='schema_version'",
|
"SELECT value FROM metadata WHERE key='schema_version'",
|
||||||
16));
|
17));
|
||||||
CHECK(query_integer(database_path,
|
CHECK(query_integer(database_path,
|
||||||
"SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
"SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
|
|
@ -1260,7 +1264,7 @@ static bool run_test(void) {
|
||||||
CHECK(create_v1_database(legacy_path));
|
CHECK(create_v1_database(legacy_path));
|
||||||
CHECK(lardon3d_project_db_open(legacy_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(legacy_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
CHECK(lardon3d_project_db_get_project(database, &loaded_project) ==
|
CHECK(lardon3d_project_db_get_project(database, &loaded_project) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
||||||
|
|
@ -1275,7 +1279,7 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(legacy_path,
|
CHECK(query_integer(legacy_path,
|
||||||
"SELECT value FROM metadata WHERE key='schema_version'",
|
"SELECT value FROM metadata WHERE key='schema_version'",
|
||||||
16));
|
17));
|
||||||
|
|
||||||
CHECK(create_v1_database(failed_migration_path));
|
CHECK(create_v1_database(failed_migration_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
||||||
|
|
@ -1306,7 +1310,7 @@ static bool run_test(void) {
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
|
v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
|
|
||||||
CHECK(create_v2_database(failed_v3_migration_path));
|
CHECK(create_v2_database(failed_v3_migration_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
|
||||||
|
|
@ -1340,7 +1344,7 @@ static bool run_test(void) {
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
|
v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
|
|
||||||
CHECK(create_v3_database(failed_v4_path));
|
CHECK(create_v3_database(failed_v4_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
|
||||||
|
|
@ -1362,7 +1366,7 @@ static bool run_test(void) {
|
||||||
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
|
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
|
||||||
}
|
}
|
||||||
CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
|
CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) ==
|
CHECK(lardon3d_project_db_load_task(database, 9, &task) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact",
|
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact",
|
||||||
|
|
@ -1417,7 +1421,7 @@ static bool run_test(void) {
|
||||||
error);
|
error);
|
||||||
}
|
}
|
||||||
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1427,7 +1431,7 @@ static bool run_test(void) {
|
||||||
5));
|
5));
|
||||||
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1436,11 +1440,11 @@ static bool run_test(void) {
|
||||||
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
||||||
CHECK(lardon3d_project_db_open(v8_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v8_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
|
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
|
|
||||||
CHECK(create_v7_database(failed_v8_path));
|
CHECK(create_v7_database(failed_v8_path));
|
||||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
|
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
|
||||||
|
|
@ -1457,7 +1461,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1476,11 +1480,11 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v10_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v10_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
|
v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v10_path,
|
query_integer(v10_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
@ -1507,7 +1511,7 @@ static bool run_test(void) {
|
||||||
1));
|
1));
|
||||||
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1521,11 +1525,11 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(v13_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(v13_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(query_integer(
|
CHECK(query_integer(
|
||||||
v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 16));
|
v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 17));
|
||||||
CHECK(
|
CHECK(
|
||||||
query_integer(v13_path,
|
query_integer(v13_path,
|
||||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||||
|
|
@ -1548,7 +1552,7 @@ static bool run_test(void) {
|
||||||
true_v14_path);
|
true_v14_path);
|
||||||
}
|
}
|
||||||
CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK &&
|
CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(
|
CHECK(
|
||||||
|
|
@ -1663,7 +1667,7 @@ static bool run_test(void) {
|
||||||
if (true_v15_result != LARDON3D_PROJECT_DB_OK)
|
if (true_v15_result != LARDON3D_PROJECT_DB_OK)
|
||||||
fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error);
|
fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error);
|
||||||
CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK);
|
CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
Lardon3DProjectDbProject migrated_project;
|
Lardon3DProjectDbProject migrated_project;
|
||||||
CHECK(lardon3d_project_db_get_project(database, &migrated_project) ==
|
CHECK(lardon3d_project_db_get_project(database, &migrated_project) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
|
@ -1695,7 +1699,7 @@ static bool run_test(void) {
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
CHECK(schema_compare(database_path, true_v15_path, true));
|
CHECK(schema_compare(database_path, true_v15_path, true));
|
||||||
|
|
@ -1730,7 +1734,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(true_v15_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(true_v15_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK);
|
LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(database) == 16);
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1749,7 +1753,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
@ -1768,7 +1772,7 @@ static bool run_test(void) {
|
||||||
0));
|
0));
|
||||||
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
|
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
|
||||||
LARDON3D_PROJECT_DB_OK &&
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
lardon3d_project_db_schema_version(database) == 16);
|
lardon3d_project_db_schema_version(database) == 17);
|
||||||
lardon3d_project_db_close(database);
|
lardon3d_project_db_close(database);
|
||||||
database = NULL;
|
database = NULL;
|
||||||
|
|
||||||
|
|
|
||||||
153
tests/test_sparse_sfm_gate_f.cpp
Normal file
153
tests/test_sparse_sfm_gate_f.cpp
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <limits>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
|
||||||
|
#include <lardon3d/sparse_sfm_incremental.h>
|
||||||
|
#include "../src/sparse_sfm_gate_f_internal.h"
|
||||||
|
|
||||||
|
#define CHECK(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
static bool decode_hex(const char *hex, unsigned char *output, size_t size) {
|
||||||
|
if (std::strlen(hex) != size * 2) return false;
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
unsigned int value = 0;
|
||||||
|
if (std::sscanf(hex + i * 2, "%2x", &value) != 1) return false;
|
||||||
|
output[i] = static_cast<unsigned char>(value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hash(const unsigned char *bytes, size_t size, unsigned char digest[32]) {
|
||||||
|
unsigned int length = 0;
|
||||||
|
return EVP_Digest(bytes, size, digest, &length, EVP_sha256(), nullptr) == 1 && length == 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
static const char golden_record_hex[] =
|
||||||
|
"4c334453464d46500100000006000000060000000600000020000000200000000010000000100000"
|
||||||
|
"40420f000000000090d003000000000000000000000000402d431cebe2361a3f000000000000f83f"
|
||||||
|
"2b8716d9cef7ef3fdc05000006000000000000000000e03f2d431cebe2361a3f000000000000e03f"
|
||||||
|
"0000000000000000000000000000f83f2b8716d9cef7ef3fe803000006000000000000000000e03f"
|
||||||
|
"00000000000000001e00000011ea2d819997713d0100000001000000010000000100000001000000"
|
||||||
|
"01000000010000000100000001000000010000000100000001000000010000000100000001000000"
|
||||||
|
"0100000001000000010000000100000095d626e80b2e113e010000000100000095d626e80b2e113e"
|
||||||
|
"01000000000000000000004001000000010000000100000001000000010000000100000001000000"
|
||||||
|
"320000008dedb5a0f7c6b03ebbbdd7d9df7cdb3d3a8c30e28e79453e000000000100000011ea2d81"
|
||||||
|
"9997713d0100000001000000";
|
||||||
|
static const char golden_digest_hex[] =
|
||||||
|
"e1c83e5b2036e49254a9426ddbace42b7831373bc896f27abdd2f61e302f9e8c";
|
||||||
|
unsigned char expected[372]{};
|
||||||
|
unsigned char expected_digest[32]{};
|
||||||
|
CHECK(decode_hex(golden_record_hex, expected, sizeof(expected)));
|
||||||
|
CHECK(decode_hex(golden_digest_hex, expected_digest, sizeof(expected_digest)));
|
||||||
|
Lardon3DSparseIncrementalParameters parameters{};
|
||||||
|
CHECK(lardon3d_sparse_incremental_parameters_default(¶meters));
|
||||||
|
unsigned char record[372]{};
|
||||||
|
unsigned char digest[32]{};
|
||||||
|
CHECK(lardon3d_sparse_sfm_fingerprint_record(¶meters, record));
|
||||||
|
CHECK(std::memcmp(record, expected, sizeof(record)) == 0);
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(¶meters, digest));
|
||||||
|
CHECK(std::memcmp(digest, expected_digest, sizeof(digest)) == 0);
|
||||||
|
unsigned char repeated[32]{};
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(¶meters, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) == 0);
|
||||||
|
|
||||||
|
Lardon3DSparseIncrementalParameters mutated = parameters;
|
||||||
|
mutated.minimum_seed_tracks++;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.maximum_tracks++;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.reprojection_threshold_px += 0.25;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.relative_pose.confidence -= 0.01;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.relative_pose.deterministic_seed++;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.pnp.confidence -= 0.01;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.pnp.deterministic_seed++;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.refinement.convergence_tolerance *= 2.0;
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
|
||||||
|
unsigned char policy_record[372];
|
||||||
|
std::memcpy(policy_record, record, sizeof(record));
|
||||||
|
policy_record[180] ^= 1;
|
||||||
|
CHECK(hash(policy_record, sizeof(policy_record), repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
std::memcpy(policy_record, record, sizeof(record));
|
||||||
|
policy_record[256] ^= 1;
|
||||||
|
CHECK(hash(policy_record, sizeof(policy_record), repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
std::memcpy(policy_record, record, sizeof(record));
|
||||||
|
policy_record[300] ^= 1;
|
||||||
|
CHECK(hash(policy_record, sizeof(policy_record), repeated));
|
||||||
|
CHECK(std::memcmp(digest, repeated, sizeof(digest)) != 0);
|
||||||
|
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.minimum_track_parallax_rad = 0.0;
|
||||||
|
CHECK(lardon3d_sparse_sfm_fingerprint_record(&mutated, record));
|
||||||
|
mutated.minimum_track_parallax_rad = -0.0;
|
||||||
|
CHECK(lardon3d_sparse_sfm_fingerprint_record(&mutated, policy_record));
|
||||||
|
CHECK(std::memcmp(record, policy_record, sizeof(record)) == 0);
|
||||||
|
mutated = parameters;
|
||||||
|
mutated.pnp.confidence = std::numeric_limits<double>::quiet_NaN();
|
||||||
|
CHECK(!lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
mutated.pnp.confidence = std::numeric_limits<double>::infinity();
|
||||||
|
CHECK(!lardon3d_sparse_sfm_parameter_fingerprint(&mutated, repeated));
|
||||||
|
|
||||||
|
Lardon3DResourceEstimate estimate{};
|
||||||
|
CHECK(lardon3d_sparse_sfm_resource_estimate(10, 100, 1000, &estimate));
|
||||||
|
CHECK(estimate.memory_fixed_bytes == 136314880ULL);
|
||||||
|
CHECK(estimate.memory_bytes_per_item == 0 && estimate.gpu_memory_fixed_bytes == 0 &&
|
||||||
|
estimate.gpu_memory_bytes_per_item == 0 && estimate.minimum_batch_size == 1 &&
|
||||||
|
estimate.maximum_batch_size == 1 && estimate.desired_cpu_threads == 1 &&
|
||||||
|
estimate.desired_gpu_slots == 0 && estimate.desired_io_slots == 1 &&
|
||||||
|
estimate.task_class == LARDON3D_RESOURCE_TASK_CPU);
|
||||||
|
CHECK(!lardon3d_sparse_sfm_resource_estimate(UINT64_MAX, 1, 1, &estimate));
|
||||||
|
CHECK(!lardon3d_sparse_sfm_resource_estimate(1, UINT64_MAX, 1, &estimate));
|
||||||
|
CHECK(!lardon3d_sparse_sfm_resource_estimate(1, 1, UINT64_MAX, &estimate));
|
||||||
|
CHECK(lardon3d_sparse_sfm_component_persistable(2, 3));
|
||||||
|
CHECK(!lardon3d_sparse_sfm_component_persistable(0, 3));
|
||||||
|
CHECK(!lardon3d_sparse_sfm_component_persistable(2, 0));
|
||||||
|
double rmse = 0.0;
|
||||||
|
double median = 0.0;
|
||||||
|
const double zero[] = {0.0};
|
||||||
|
CHECK(lardon3d_sparse_sfm_publication_metrics(zero, 1, &rmse, &median));
|
||||||
|
CHECK(rmse == 0.0 && median == 0.0);
|
||||||
|
const double odd[] = {1.0, 9.0, 4.0};
|
||||||
|
CHECK(lardon3d_sparse_sfm_publication_metrics(odd, 3, &rmse, &median));
|
||||||
|
CHECK(std::abs(rmse - std::sqrt(14.0 / 3.0)) < 1e-15 && median == 2.0);
|
||||||
|
const double even[] = {1.0, 16.0, 4.0, 9.0};
|
||||||
|
CHECK(lardon3d_sparse_sfm_publication_metrics(even, 4, &rmse, &median));
|
||||||
|
CHECK(std::abs(rmse - std::sqrt(7.5)) < 1e-15 && median == 2.5);
|
||||||
|
const double weighted[] = {1.0, 1.0, 100.0};
|
||||||
|
CHECK(lardon3d_sparse_sfm_publication_metrics(weighted, 3, &rmse, &median));
|
||||||
|
CHECK(std::abs(rmse - std::sqrt(34.0)) < 1e-15 && median == 1.0);
|
||||||
|
const double invalid[] = {1.0, std::numeric_limits<double>::infinity()};
|
||||||
|
CHECK(!lardon3d_sparse_sfm_publication_metrics(invalid, 2, &rmse, &median));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
@ -91,7 +91,7 @@ static bool run_test(void) {
|
||||||
Lardon3DProjectDb *db = NULL;
|
Lardon3DProjectDb *db = NULL;
|
||||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK);
|
CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
CHECK(lardon3d_project_db_schema_version(db) == 16);
|
CHECK(lardon3d_project_db_schema_version(db) == 17);
|
||||||
|
|
||||||
Lardon3DProjectDbScanSet scanset;
|
Lardon3DProjectDbScanSet scanset;
|
||||||
CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) ==
|
CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) ==
|
||||||
|
|
|
||||||
142
tests/test_sparse_sfm_payload.cpp
Normal file
142
tests/test_sparse_sfm_payload.cpp
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sqlite3.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/project_db.h>
|
||||||
|
#include <lardon3d/sparse_sfm_task.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CHECK(condition) \
|
||||||
|
do { \
|
||||||
|
if (!(condition)) { \
|
||||||
|
std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #condition); \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
} while (false)
|
||||||
|
|
||||||
|
static bool same_double(double a, double b) {
|
||||||
|
uint64_t bits_a = 0;
|
||||||
|
uint64_t bits_b = 0;
|
||||||
|
std::memcpy(&bits_a, &a, sizeof(bits_a));
|
||||||
|
std::memcpy(&bits_b, &b, sizeof(bits_b));
|
||||||
|
return bits_a == bits_b;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool same_parameters(const Lardon3DSparseIncrementalParameters &a,
|
||||||
|
const Lardon3DSparseIncrementalParameters &b) {
|
||||||
|
return a.minimum_seed_tracks == b.minimum_seed_tracks &&
|
||||||
|
a.minimum_seed_landmarks == b.minimum_seed_landmarks &&
|
||||||
|
a.minimum_pnp_correspondences == b.minimum_pnp_correspondences &&
|
||||||
|
a.maximum_seed_candidates == b.maximum_seed_candidates &&
|
||||||
|
a.maximum_registration_rounds == b.maximum_registration_rounds &&
|
||||||
|
a.maximum_landmarks_per_round == b.maximum_landmarks_per_round &&
|
||||||
|
a.maximum_images == b.maximum_images &&
|
||||||
|
a.maximum_observations == b.maximum_observations && a.maximum_tracks == b.maximum_tracks &&
|
||||||
|
same_double(a.reprojection_threshold_px, b.reprojection_threshold_px) &&
|
||||||
|
same_double(a.minimum_track_parallax_rad, b.minimum_track_parallax_rad) &&
|
||||||
|
same_double(a.relative_pose.robust_threshold_px, b.relative_pose.robust_threshold_px) &&
|
||||||
|
same_double(a.relative_pose.confidence, b.relative_pose.confidence) &&
|
||||||
|
a.relative_pose.max_iterations == b.relative_pose.max_iterations &&
|
||||||
|
a.relative_pose.minimum_inliers == b.relative_pose.minimum_inliers &&
|
||||||
|
same_double(a.relative_pose.minimum_inlier_ratio,
|
||||||
|
b.relative_pose.minimum_inlier_ratio) &&
|
||||||
|
same_double(a.relative_pose.minimum_parallax_rad, b.relative_pose.minimum_parallax_rad) &&
|
||||||
|
same_double(a.relative_pose.minimum_cheirality_ratio,
|
||||||
|
b.relative_pose.minimum_cheirality_ratio) &&
|
||||||
|
a.relative_pose.deterministic_seed == b.relative_pose.deterministic_seed &&
|
||||||
|
same_double(a.pnp.reprojection_threshold_px, b.pnp.reprojection_threshold_px) &&
|
||||||
|
same_double(a.pnp.confidence, b.pnp.confidence) &&
|
||||||
|
a.pnp.max_iterations == b.pnp.max_iterations &&
|
||||||
|
a.pnp.minimum_inliers == b.pnp.minimum_inliers &&
|
||||||
|
same_double(a.pnp.minimum_inlier_ratio, b.pnp.minimum_inlier_ratio) &&
|
||||||
|
a.pnp.deterministic_seed == b.pnp.deterministic_seed &&
|
||||||
|
a.refinement.max_iterations == b.refinement.max_iterations &&
|
||||||
|
same_double(a.refinement.convergence_tolerance, b.refinement.convergence_tolerance);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
char directory[] = "/tmp/lardon3d-sfm-payload-XXXXXX";
|
||||||
|
CHECK(mkdtemp(directory) != nullptr);
|
||||||
|
std::string path = std::string(directory) + "/project.db";
|
||||||
|
Lardon3DProjectDb *database = nullptr;
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||||
|
CHECK(lardon3d_project_db_open(path.c_str(), &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_schema_version(database) == 17);
|
||||||
|
|
||||||
|
sqlite3 *raw = nullptr;
|
||||||
|
CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK);
|
||||||
|
const char *fixture_sql =
|
||||||
|
"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(41,'track_builder',1,zeroblob(32),1,1,zeroblob(32),zeroblob(32),1,0,0);"
|
||||||
|
"INSERT INTO sparse_calibration_scopes(scope_id,scientific_hash,member_count) "
|
||||||
|
"VALUES(43,randomblob(32),1);";
|
||||||
|
CHECK(sqlite3_exec(raw, fixture_sql, nullptr, nullptr, nullptr) == SQLITE_OK);
|
||||||
|
sqlite3_close(raw);
|
||||||
|
|
||||||
|
const uint64_t values[] = {0, 1, static_cast<uint64_t>(INT64_MAX),
|
||||||
|
static_cast<uint64_t>(INT64_MAX) + 1, UINT64_MAX};
|
||||||
|
for (size_t index = 0; index < sizeof(values) / sizeof(values[0]); ++index) {
|
||||||
|
Lardon3DSparseIncrementalParameters p{};
|
||||||
|
CHECK(lardon3d_sparse_incremental_parameters_default(&p));
|
||||||
|
p.maximum_observations = values[index];
|
||||||
|
p.maximum_tracks = values[index];
|
||||||
|
p.relative_pose.deterministic_seed = values[index];
|
||||||
|
p.pnp.deterministic_seed = values[index];
|
||||||
|
p.reprojection_threshold_px = 3.125;
|
||||||
|
p.relative_pose.confidence = 0.9125;
|
||||||
|
p.pnp.confidence = 0.8875;
|
||||||
|
uint64_t id = 100 + index;
|
||||||
|
Lardon3DTaskDurableSnapshot snapshot{};
|
||||||
|
snapshot.id = id;
|
||||||
|
std::snprintf(snapshot.name, sizeof(snapshot.name), "Sparse payload %zu", index);
|
||||||
|
snapshot.saved_state = TASK_PENDING;
|
||||||
|
snapshot.recovery_state = TASK_PENDING;
|
||||||
|
snapshot.estimate = {134217728, 0, 0, 0, 1, 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_CPU};
|
||||||
|
Lardon3DProjectDbSparseSfmTask payload{id, 41, 43, 1, 1, p};
|
||||||
|
CHECK(lardon3d_project_db_record_sparse_sfm_task(
|
||||||
|
database, &snapshot, LARDON3D_SPARSE_SFM_TASK_KIND,
|
||||||
|
LARDON3D_SPARSE_SFM_TASK_KIND_VERSION, nullptr, &payload, 0) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DProjectDbSparseSfmTask loaded{};
|
||||||
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(database, id, &loaded) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(loaded.task_id == id && loaded.track_set_id == 41 &&
|
||||||
|
loaded.calibration_scope_id == 43 && loaded.sfm_kind == 1 && loaded.sfm_version == 1);
|
||||||
|
CHECK(same_parameters(payload.parameters, loaded.parameters));
|
||||||
|
}
|
||||||
|
Lardon3DSparseIncrementalParameters p{};
|
||||||
|
CHECK(lardon3d_sparse_incremental_parameters_default(&p));
|
||||||
|
Lardon3DTaskDurableSnapshot wrong{};
|
||||||
|
wrong.id = 999;
|
||||||
|
std::snprintf(wrong.name, sizeof(wrong.name), "Wrong kind");
|
||||||
|
wrong.saved_state = TASK_PENDING;
|
||||||
|
wrong.recovery_state = TASK_PENDING;
|
||||||
|
wrong.estimate = {134217728, 0, 0, 0, 1, 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_CPU};
|
||||||
|
Lardon3DProjectDbSparseSfmTask payload{999, 41, 43, 1, 1, p};
|
||||||
|
CHECK(lardon3d_project_db_record_sparse_sfm_task(database, &wrong, "other.run", 1, nullptr,
|
||||||
|
&payload, 0) ==
|
||||||
|
LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||||
|
CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK);
|
||||||
|
CHECK(sqlite3_exec(raw, "PRAGMA ignore_check_constraints=ON; UPDATE sparse_sfm_tasks SET "
|
||||||
|
"maximum_tracks=zeroblob(7) WHERE task_id=100;",
|
||||||
|
nullptr, nullptr, nullptr) == SQLITE_OK);
|
||||||
|
sqlite3_close(raw);
|
||||||
|
Lardon3DProjectDbSparseSfmTask corrupt{};
|
||||||
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(database, 100, &corrupt) ==
|
||||||
|
LARDON3D_PROJECT_DB_CORRUPT);
|
||||||
|
CHECK(sqlite3_open(path.c_str(), &raw) == SQLITE_OK);
|
||||||
|
CHECK(sqlite3_exec(raw, "PRAGMA foreign_keys=ON; DELETE FROM tasks WHERE task_id=101;",
|
||||||
|
nullptr, nullptr, nullptr) == SQLITE_OK);
|
||||||
|
sqlite3_close(raw);
|
||||||
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(database, 101, &corrupt) ==
|
||||||
|
LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
CHECK(unlink(path.c_str()) == 0);
|
||||||
|
CHECK(rmdir(directory) == 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
368
tests/test_sparse_sfm_task.cpp
Normal file
368
tests/test_sparse_sfm_task.cpp
Normal file
|
|
@ -0,0 +1,368 @@
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sched.h>
|
||||||
|
#include <string>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <lardon3d/app_state.h>
|
||||||
|
#include <lardon3d/feature_store.h>
|
||||||
|
#include <lardon3d/project_db.h>
|
||||||
|
#include <lardon3d/resource_governor.h>
|
||||||
|
#include <lardon3d/sparse_sfm_model.h>
|
||||||
|
#include <lardon3d/sparse_sfm_task.h>
|
||||||
|
#include <lardon3d/task_checkpoint.h>
|
||||||
|
#include <lardon3d/task_kind_registry.h>
|
||||||
|
#include <lardon3d/task_queue.h>
|
||||||
|
}
|
||||||
|
#include "../src/sparse_sfm_gate_f_internal.h"
|
||||||
|
|
||||||
|
extern "C" int lardon3d_sparse_sfm_task_test_observation_lookup(
|
||||||
|
const Lardon3DSparseIncrementalObservation *observations, size_t count,
|
||||||
|
uint64_t track_id, uint64_t image_id, uint64_t feature_set_id,
|
||||||
|
uint32_t feature_index, size_t *resolved_index);
|
||||||
|
extern "C" bool lardon3d_sparse_sfm_task_test_project_landmarks(
|
||||||
|
const Lardon3DSparseIncrementalLandmark *landmarks, size_t landmark_count,
|
||||||
|
const uint64_t *component_keys, size_t component_count,
|
||||||
|
Lardon3DSparseLandmark *output, size_t output_capacity, size_t *output_count);
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
#define CHECK(value) \
|
||||||
|
do { \
|
||||||
|
if (!(value)) { \
|
||||||
|
std::fprintf(stderr, "sparse-sfm-task:%d: %s\n", __LINE__, #value); \
|
||||||
|
return false; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
constexpr size_t kImageCount = 3;
|
||||||
|
constexpr size_t kTrackCount = 12;
|
||||||
|
|
||||||
|
struct Fixture {
|
||||||
|
std::string root;
|
||||||
|
std::string database_path;
|
||||||
|
Lardon3DAppState state{};
|
||||||
|
Lardon3DProjectDbScanSet scanset{};
|
||||||
|
Lardon3DProjectDbImage images[kImageCount]{};
|
||||||
|
Lardon3DProjectDbFeatureSet feature_sets[kImageCount]{};
|
||||||
|
Lardon3DProjectDbTrackSet track_set{};
|
||||||
|
Lardon3DSparseCalibrationScope scope{};
|
||||||
|
Lardon3DSparseIncrementalParameters parameters{};
|
||||||
|
};
|
||||||
|
|
||||||
|
Lardon3DResourcePolicy policy() {
|
||||||
|
return {0, 0, 0, 0, 1.0, 100.0, 100.0, 100.0, 0, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wait_state(Lardon3DTaskQueue *queue, uint64_t id, Lardon3DTaskState wanted) {
|
||||||
|
for (size_t attempt = 0; attempt < 4000000; ++attempt) {
|
||||||
|
Lardon3DTaskSnapshot snapshot{};
|
||||||
|
if (lardon3d_task_queue_get(queue, id, &snapshot) && snapshot.state == wanted) return true;
|
||||||
|
sched_yield();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DSparseGeometryPoint2 project(const Lardon3DSparseGeometryCalibration &calibration,
|
||||||
|
size_t camera,
|
||||||
|
const Lardon3DSparseGeometryPoint3 &point) {
|
||||||
|
double x = point.x + static_cast<double>(camera);
|
||||||
|
return {calibration.fx * x / point.z + calibration.cx,
|
||||||
|
calibration.fy * point.y / point.z + calibration.cy};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string asset_path(const unsigned char hash[32], const char *kind) {
|
||||||
|
static constexpr char digits[] = "0123456789abcdef";
|
||||||
|
std::string hex(64, '0');
|
||||||
|
for (size_t index = 0; index < 32; ++index) {
|
||||||
|
hex[2 * index] = digits[hash[index] >> 4];
|
||||||
|
hex[2 * index + 1] = digits[hash[index] & 15U];
|
||||||
|
}
|
||||||
|
return std::string("assets/") + kind + "/" + hex.substr(0, 2) + "/" + hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool initialize_runtime(Fixture *fixture) {
|
||||||
|
lardon3d_app_state_init(&fixture->state);
|
||||||
|
fixture->state.hardware_profile.logical_cpu_count = 16;
|
||||||
|
fixture->state.hardware_profile.page_size_bytes = 4096;
|
||||||
|
fixture->state.hardware_profile.memory_total_bytes = 64ULL * 1024 * 1024 * 1024;
|
||||||
|
std::snprintf(fixture->state.hardware_profile.cpu_architecture,
|
||||||
|
sizeof(fixture->state.hardware_profile.cpu_architecture), "test");
|
||||||
|
auto resource_policy = policy();
|
||||||
|
fixture->state.resource_governor =
|
||||||
|
lardon3d_resource_governor_create(&fixture->state.hardware_profile, &resource_policy);
|
||||||
|
fixture->state.task_queue = fixture->state.resource_governor
|
||||||
|
? lardon3d_task_queue_create(
|
||||||
|
fixture->state.resource_governor, 8)
|
||||||
|
: nullptr;
|
||||||
|
return fixture->state.task_queue != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_fixture(Fixture *fixture) {
|
||||||
|
char root[] = "/tmp/lardon3d-sparse-task-XXXXXX";
|
||||||
|
CHECK(mkdtemp(root) != nullptr);
|
||||||
|
fixture->root = root;
|
||||||
|
fixture->database_path = fixture->root + "/project.db";
|
||||||
|
CHECK(mkdir((fixture->root + "/.lardon3d").c_str(), 0700) == 0);
|
||||||
|
CHECK(mkdir((fixture->root + "/.lardon3d/checkpoints").c_str(), 0700) == 0);
|
||||||
|
CHECK(initialize_runtime(fixture));
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||||
|
CHECK(lardon3d_project_db_open(fixture->database_path.c_str(),
|
||||||
|
&fixture->state.project_db, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
fixture->state.project_loaded = true;
|
||||||
|
CHECK(std::snprintf(fixture->state.project_path, sizeof(fixture->state.project_path), "%s",
|
||||||
|
fixture->root.c_str()) > 0);
|
||||||
|
CHECK(lardon3d_project_db_create_scanset(fixture->state.project_db, "sfm-task",
|
||||||
|
&fixture->scanset) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
||||||
|
const Lardon3DSparseGeometryCalibration geometry = {
|
||||||
|
4000, 3000, 2000.0, 2000.0, 2000.0, 1500.0, 0.0, 0.0, 0.0, 0.0};
|
||||||
|
std::vector<Lardon3DSparseGeometryPoint3> points;
|
||||||
|
for (size_t track = 0; track < kTrackCount; ++track)
|
||||||
|
points.push_back({-1.0 + static_cast<double>(track % 4) * 0.5,
|
||||||
|
-0.8 + static_cast<double>(track / 4) * 0.4,
|
||||||
|
5.0 + static_cast<double>(track % 3) * 0.25});
|
||||||
|
|
||||||
|
unsigned char feature_fingerprint[32]{};
|
||||||
|
for (size_t camera = 0; camera < kImageCount; ++camera) {
|
||||||
|
unsigned char image_hash[32]{};
|
||||||
|
image_hash[0] = static_cast<unsigned char>(camera + 1);
|
||||||
|
std::string relative = asset_path(image_hash, "images");
|
||||||
|
Lardon3DProjectDbImageRegisterStatus status{};
|
||||||
|
CHECK(lardon3d_project_db_register_image(
|
||||||
|
fixture->state.project_db, fixture->scanset.scanset_id, image_hash,
|
||||||
|
relative.c_str(), 1, "synthetic", relative.c_str(), 0, 1, &status,
|
||||||
|
&fixture->images[camera]) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
std::vector<Lardon3DFeatureKeypoint> keypoints(kTrackCount);
|
||||||
|
std::vector<unsigned char> descriptors(kTrackCount * 32);
|
||||||
|
for (size_t track = 0; track < kTrackCount; ++track) {
|
||||||
|
auto pixel = project(geometry, camera, points[track]);
|
||||||
|
keypoints[track].x = static_cast<float>(pixel.x);
|
||||||
|
keypoints[track].y = static_cast<float>(pixel.y);
|
||||||
|
keypoints[track].size = 1.0F;
|
||||||
|
std::memset(descriptors.data() + track * 32, static_cast<int>(track + camera), 32);
|
||||||
|
}
|
||||||
|
Lardon3DExtractedFeatures features{};
|
||||||
|
features.image_width = 4000;
|
||||||
|
features.image_height = 3000;
|
||||||
|
features.feature_count = static_cast<uint32_t>(kTrackCount);
|
||||||
|
features.keypoints = keypoints.data();
|
||||||
|
features.descriptors = descriptors.data();
|
||||||
|
features.descriptor_bytes = descriptors.size();
|
||||||
|
CHECK(lardon3d_feature_store_publish_v2(
|
||||||
|
&fixture->state, fixture->images[camera].image_id, 0, "test", 1,
|
||||||
|
feature_fingerprint, LARDON3D_FEATURE_DESCRIPTOR_U8, 32, 0, &features,
|
||||||
|
&fixture->feature_sets[camera]) == LARDON3D_FEATURE_STORE_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DSparseCalibration calibration{};
|
||||||
|
calibration.model_kind = LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE;
|
||||||
|
calibration.model_version = LARDON3D_SPARSE_SFM_CALIBRATION_VERSION;
|
||||||
|
calibration.width = 4000;
|
||||||
|
calibration.height = 3000;
|
||||||
|
calibration.fx = 2000.0;
|
||||||
|
calibration.fy = 2000.0;
|
||||||
|
calibration.cx = 2000.0;
|
||||||
|
calibration.cy = 1500.0;
|
||||||
|
calibration.provenance_kind = LARDON3D_SPARSE_SFM_PROVENANCE_USER_EXPLICIT;
|
||||||
|
Lardon3DSparseCalibration stored{};
|
||||||
|
CHECK(lardon3d_sparse_calibration_create(fixture->state.project_db, &calibration, &stored) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DSparseCalibrationMember members[kImageCount]{};
|
||||||
|
for (size_t camera = 0; camera < kImageCount; ++camera) {
|
||||||
|
members[camera].image_id = fixture->images[camera].image_id;
|
||||||
|
members[camera].calibration_id = stored.calibration_id;
|
||||||
|
std::memcpy(members[camera].calibration_hash, stored.scientific_hash, 32);
|
||||||
|
}
|
||||||
|
CHECK(lardon3d_sparse_calibration_scope_create(fixture->state.project_db, members, kImageCount,
|
||||||
|
&fixture->scope) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
|
||||||
|
std::vector<Lardon3DProjectDbTrackObservation> observations(kTrackCount * kImageCount);
|
||||||
|
std::vector<Lardon3DProjectDbTrack> tracks(kTrackCount);
|
||||||
|
for (size_t track = 0; track < kTrackCount; ++track) {
|
||||||
|
tracks[track].observation_count = kImageCount;
|
||||||
|
tracks[track].observations = observations.data() + track * kImageCount;
|
||||||
|
for (size_t camera = 0; camera < kImageCount; ++camera)
|
||||||
|
tracks[track].observations[camera] = {
|
||||||
|
fixture->feature_sets[camera].feature_set_id, static_cast<uint32_t>(track),
|
||||||
|
static_cast<uint32_t>(camera)};
|
||||||
|
}
|
||||||
|
Lardon3DProjectDbTrackSet configuration{};
|
||||||
|
std::snprintf(configuration.builder_kind, sizeof(configuration.builder_kind), "test");
|
||||||
|
configuration.builder_version = 1;
|
||||||
|
configuration.verifier_kind = 1;
|
||||||
|
configuration.verifier_version = 1;
|
||||||
|
configuration.gvr_count = 1;
|
||||||
|
configuration.track_count = kTrackCount;
|
||||||
|
CHECK(lardon3d_project_db_create_track_set(fixture->state.project_db, &configuration,
|
||||||
|
tracks.data(), tracks.size(),
|
||||||
|
&fixture->track_set) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_sparse_incremental_parameters_default(&fixture->parameters));
|
||||||
|
fixture->parameters.minimum_seed_tracks = 6;
|
||||||
|
fixture->parameters.minimum_seed_landmarks = 6;
|
||||||
|
fixture->parameters.minimum_pnp_correspondences = 6;
|
||||||
|
fixture->parameters.maximum_registration_rounds = 8;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void destroy_runtime(Fixture *fixture) {
|
||||||
|
if (fixture->state.task_queue) lardon3d_task_queue_destroy(fixture->state.task_queue);
|
||||||
|
if (fixture->state.resource_governor)
|
||||||
|
lardon3d_resource_governor_destroy(fixture->state.resource_governor);
|
||||||
|
if (fixture->state.project_db) lardon3d_project_db_close(fixture->state.project_db);
|
||||||
|
fixture->state = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DSparseSfmTaskConfiguration configuration(Fixture *fixture) {
|
||||||
|
return {fixture->track_set.track_set_id, fixture->scope.scope_id, fixture->parameters};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_lookup() {
|
||||||
|
Lardon3DSparseIncrementalObservation values[3] = {
|
||||||
|
{1, 10, 100, 1, 8, 0, 0}, {2, 20, 100, 2, 8, 0, 0},
|
||||||
|
{3, 30, 200, 1, 8, 0, 0}};
|
||||||
|
size_t index = 99;
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 10, 100, 1,
|
||||||
|
&index) == 1 && index == 0);
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 2, 20, 100, 2,
|
||||||
|
&index) == 1 && index == 1);
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 3, 30, 200, 1,
|
||||||
|
&index) == 1 && index == 2);
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 10, 999, 1,
|
||||||
|
&index) == 0);
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 99, 10, 100, 1,
|
||||||
|
&index) == 0);
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 99, 100, 1,
|
||||||
|
&index) == 0);
|
||||||
|
values[2].feature_set_id = 100;
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 10, 100, 1,
|
||||||
|
&index) == -1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_landmark_projection_order() {
|
||||||
|
Lardon3DSparseIncrementalLandmark landmarks[5]{};
|
||||||
|
const uint64_t component_keys[2] = {5, 10};
|
||||||
|
const uint64_t input_components[5] = {10, 5, 99, 5, 10};
|
||||||
|
const uint64_t input_tracks[5] = {2, 100, 3, 7, 1};
|
||||||
|
for (size_t index = 0; index < 5; ++index) {
|
||||||
|
landmarks[index].component_key = input_components[index];
|
||||||
|
landmarks[index].track_id = input_tracks[index];
|
||||||
|
landmarks[index].point.z = 1.0;
|
||||||
|
landmarks[index].observation_count = 2;
|
||||||
|
}
|
||||||
|
Lardon3DSparseLandmark projected[5]{};
|
||||||
|
size_t count = 0;
|
||||||
|
CHECK(lardon3d_sparse_sfm_task_test_project_landmarks(
|
||||||
|
landmarks, 5, component_keys, 2, projected, 5, &count));
|
||||||
|
CHECK(count == 4);
|
||||||
|
CHECK(projected[0].component_key == 5 && projected[0].track_id == 7);
|
||||||
|
CHECK(projected[1].component_key == 5 && projected[1].track_id == 100);
|
||||||
|
CHECK(projected[2].component_key == 10 && projected[2].track_id == 1);
|
||||||
|
CHECK(projected[3].component_key == 10 && projected[3].track_id == 2);
|
||||||
|
for (size_t index = 0; index < count; ++index)
|
||||||
|
CHECK(projected[index].component_key != 99);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool test_task() {
|
||||||
|
Fixture fixture;
|
||||||
|
CHECK(create_fixture(&fixture));
|
||||||
|
auto config = configuration(&fixture);
|
||||||
|
uint64_t task_id = 0;
|
||||||
|
Lardon3DTask *task = lardon3d_project_create_sparse_sfm_task(&fixture.state, &config, &task_id);
|
||||||
|
CHECK(task && task_id != 0);
|
||||||
|
char kind[LARDON3D_TASK_KIND_CAPACITY]{};
|
||||||
|
uint32_t kind_version = 0;
|
||||||
|
CHECK(lardon3d_task_kind(task, kind, &kind_version));
|
||||||
|
CHECK(std::strcmp(kind, LARDON3D_SPARSE_SFM_TASK_KIND) == 0 && kind_version == 1);
|
||||||
|
Lardon3DResourceEstimate estimate{};
|
||||||
|
CHECK(lardon3d_task_resource_estimate(task, &estimate));
|
||||||
|
CHECK(estimate.memory_fixed_bytes == 135266304 && estimate.desired_cpu_threads == 1 &&
|
||||||
|
estimate.desired_io_slots == 1 && estimate.minimum_batch_size == 1 &&
|
||||||
|
estimate.maximum_batch_size == 1);
|
||||||
|
Lardon3DProjectDbSparseSfmTask durable{};
|
||||||
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(fixture.state.project_db, task_id, &durable) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, task, nullptr));
|
||||||
|
CHECK(wait_state(fixture.state.task_queue, task_id, TASK_COMPLETED));
|
||||||
|
|
||||||
|
unsigned char fingerprint[32]{};
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&fixture.parameters, fingerprint));
|
||||||
|
Lardon3DSparseReconstruction reconstruction{};
|
||||||
|
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
||||||
|
fixture.state.project_db, fixture.track_set.track_set_id, fixture.scope.scope_id, 1, 1,
|
||||||
|
fingerprint, &reconstruction) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(reconstruction.component_count == 1 && reconstruction.registered_image_count == 3 &&
|
||||||
|
reconstruction.landmark_count >= 6 &&
|
||||||
|
std::isfinite(reconstruction.reprojection_rmse_px) &&
|
||||||
|
std::isfinite(reconstruction.reprojection_median_px));
|
||||||
|
Lardon3DSparseLandmarkObservation persisted_observations[64]{};
|
||||||
|
Lardon3DSparseObservationPage observation_page{};
|
||||||
|
observation_page.items = persisted_observations;
|
||||||
|
CHECK(lardon3d_sparse_observation_list(fixture.state.project_db,
|
||||||
|
reconstruction.reconstruction_id, 0, 0, 64,
|
||||||
|
&observation_page) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(observation_page.count >= 18);
|
||||||
|
|
||||||
|
uint64_t reuse_id = 0;
|
||||||
|
CHECK(lardon3d_project_enqueue_sparse_sfm_task(&fixture.state, &config, &reuse_id));
|
||||||
|
CHECK(wait_state(fixture.state.task_queue, reuse_id, TASK_COMPLETED));
|
||||||
|
Lardon3DSparseReconstruction reused{};
|
||||||
|
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
||||||
|
fixture.state.project_db, fixture.track_set.track_set_id, fixture.scope.scope_id, 1, 1,
|
||||||
|
fingerprint, &reused) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(reused.reconstruction_id == reconstruction.reconstruction_id);
|
||||||
|
|
||||||
|
uint64_t restart_id = 0;
|
||||||
|
Lardon3DTask *pending =
|
||||||
|
lardon3d_project_create_sparse_sfm_task(&fixture.state, &config, &restart_id);
|
||||||
|
CHECK(pending != nullptr);
|
||||||
|
Lardon3DTaskDurableSnapshot before{};
|
||||||
|
CHECK(lardon3d_task_durable_snapshot(pending, &before));
|
||||||
|
lardon3d_task_destroy(pending);
|
||||||
|
lardon3d_project_db_close(fixture.state.project_db);
|
||||||
|
fixture.state.project_db = nullptr;
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||||
|
CHECK(lardon3d_project_db_open(fixture.database_path.c_str(), &fixture.state.project_db, error) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DTaskKindDescriptor descriptor{LARDON3D_SPARSE_SFM_TASK_KIND, 1,
|
||||||
|
lardon3d_sparse_sfm_task_reconstruct};
|
||||||
|
Lardon3DTaskKindRegistry registry{};
|
||||||
|
CHECK(lardon3d_task_kind_registry_init(®istry, &descriptor, 1));
|
||||||
|
Lardon3DTaskReconstructionContext runtime{fixture.root.c_str(), fixture.state.project_db,
|
||||||
|
fixture.state.resource_governor, nullptr};
|
||||||
|
Lardon3DTask *restored = nullptr;
|
||||||
|
CHECK(lardon3d_task_kind_registry_restore(®istry, LARDON3D_SPARSE_SFM_TASK_KIND, 1,
|
||||||
|
&before, &runtime, &restored) ==
|
||||||
|
LARDON3D_TASK_KIND_OK);
|
||||||
|
Lardon3DResourceEstimate restored_estimate{};
|
||||||
|
CHECK(lardon3d_task_resource_estimate(restored, &restored_estimate));
|
||||||
|
CHECK(std::memcmp(&estimate, &restored_estimate, sizeof(estimate)) == 0);
|
||||||
|
Lardon3DProjectDbSparseSfmTask restored_payload{};
|
||||||
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(fixture.state.project_db, restart_id,
|
||||||
|
&restored_payload) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
unsigned char restored_fingerprint[32]{};
|
||||||
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&restored_payload.parameters,
|
||||||
|
restored_fingerprint));
|
||||||
|
CHECK(std::memcmp(fingerprint, restored_fingerprint, 32) == 0);
|
||||||
|
unsigned char record_before[372]{};
|
||||||
|
unsigned char record_after[372]{};
|
||||||
|
CHECK(lardon3d_sparse_sfm_fingerprint_record(&fixture.parameters, record_before));
|
||||||
|
CHECK(lardon3d_sparse_sfm_fingerprint_record(&restored_payload.parameters, record_after));
|
||||||
|
CHECK(std::memcmp(record_before, record_after, sizeof(record_before)) == 0);
|
||||||
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, restored, nullptr));
|
||||||
|
CHECK(wait_state(fixture.state.task_queue, restart_id, TASK_COMPLETED));
|
||||||
|
destroy_runtime(&fixture);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return test_lookup() && test_landmark_projection_order() && test_task() ? 0 : 1;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue