feat: freeze calibration bootstrap and selected execution
This commit is contained in:
parent
892d0c5954
commit
08a45a0724
25 changed files with 2424 additions and 65 deletions
|
|
@ -34,7 +34,9 @@ through an explicitly authorized, explicitly scoped human ticket:
|
|||
- F0 — PASS/FROZEN
|
||||
- Phase H v1 — PASS/FROZEN
|
||||
- MVS-M1 — PASS/FROZEN
|
||||
- Project DB v21 — PASS/FROZEN
|
||||
- Project DB v22 — PASS/FROZEN
|
||||
- Calibration Bootstrap v1 — PASS/FROZEN
|
||||
- Selected Scientific Execution — PASS/FROZEN
|
||||
- Photo Quality Triage / Acquisition Selection — PASS/FROZEN
|
||||
- S1 Capture / Asset Provenance — PASS/FROZEN
|
||||
- S2 Capture-safe Standard Ingestion — PASS/FROZEN
|
||||
|
|
|
|||
25
README.md
25
README.md
|
|
@ -34,10 +34,14 @@ persistante, enrichissable et versionnable.
|
|||
- **Image View** : vues triées et filtrées pour la TUI
|
||||
- **Task** : moteur de tâches avec pause/reprise, annulation et séquences
|
||||
- **Task Checkpoint v1** : snapshot durable, fichier atomique et reprise sûre
|
||||
- **Project Database v21** : fondations v20 préservées, avec persistance additive
|
||||
des résultats de triage photo
|
||||
- **Project Database v22** : fondations v20/v21 préservées, avec snapshot
|
||||
d'exécution sélectionnée, publication bornée des représentations, attache de
|
||||
scope de calibration et tâche durable mince `raw.develop` — PASS / FROZEN
|
||||
- **[Photo Quality Triage](docs/architecture/photo_quality_triage.md)** : métriques JPEG
|
||||
bornées et recommandations non destructives, avec persistance additive v21
|
||||
- **[Calibration Bootstrap v1](docs/architecture/calibration_bootstrap.md)** :
|
||||
import borné d'une calibration optimisée et traçable avant le Sparse SfM à
|
||||
calibration connue — PASS / FROZEN ; ni
|
||||
auto-calibration interne ni EXIF comme source de calibration scientifique
|
||||
- **Exécution durable de campagne d'acquisition** : tâche générique à requête
|
||||
typée immuable, confirmations `CALLER_EXPLICIT`, curseur et correspondance
|
||||
tâche/groupe→Capture persistants ; un groupe S3-E par séquence, reprise par
|
||||
|
|
@ -72,7 +76,11 @@ persistante, enrichissable et versionnable.
|
|||
réels A6000 (953 paires confirmées `CALLER_EXPLICIT`) et Samsung SM-G990B
|
||||
(3544 JPEG singleton) ont été validés dans deux ScanSets d'un même projet
|
||||
temporaire, avec exécution durable, Governor/Queue et reprise sans Capture
|
||||
dupliqué. La suite reste le pipeline scientifique aval, selon la
|
||||
dupliqué. Les campagnes réelles actuellement évaluées sont
|
||||
`CALIBRATION_UNAVAILABLE`, donc le Sparse SfM réel est
|
||||
`BLOCKED_BY_KNOWN_CALIBRATION_DATA` : ce n'est ni un échec logiciel, ni un
|
||||
rejet de qualité, ni une autorisation d'importer une pseudo-calibration. La
|
||||
suite reste le pipeline scientifique aval, selon la
|
||||
[roadmap canonique](docs/roadmap/roadmap.md).
|
||||
|
||||
### Plus tard / différé
|
||||
|
|
@ -210,6 +218,15 @@ les paramètres dans `L3DMDID2` v2 (220 octets). Chaque appel utilise un espace
|
|||
travail privé neuf sous le staging appelant, sans réemploi d'une scène,
|
||||
profondeur, cache ou sortie antérieure. Le DAG, le viewer et les autres étapes
|
||||
denses restent des tickets séparés planifiés.
|
||||
Project DB v22, `raw.develop` et Calibration Bootstrap v1 sont **PASS /
|
||||
FROZEN** : la suite normale 53/53, les contrôles syntaxiques C17, `git diff
|
||||
--check`, la validation ciblée ASan/UBSan et l'audit final ont passé. La suite
|
||||
ASan/UBSan complète demeure qualifiée par le comportement LSan du pilote tiers
|
||||
RADV ; elle n'est pas présentée comme un PASS complet du dépôt. Les campagnes
|
||||
réelles S21 et A6000 Engine Bay sont
|
||||
`CALIBRATION_UNAVAILABLE` par non-identifiabilité scientifique des données de
|
||||
calibration connues ; le Sparse SfM réel reste
|
||||
`BLOCKED_BY_KNOWN_CALIBRATION_DATA`, sans pseudo-calibration ni import inféré.
|
||||
Le Resource Governor ne constitue pas un Resource System générique : voir la décision
|
||||
d’architecture.
|
||||
|
||||
|
|
|
|||
148
docs/architecture/calibration_bootstrap.md
Normal file
148
docs/architecture/calibration_bootstrap.md
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
# Calibration Bootstrap v1
|
||||
|
||||
**PASS / FROZEN.** Project DB v22 implements the
|
||||
immutable selected-execution snapshot, explicit cross-task mappings, representation cursor and
|
||||
calibration-scope attachment described below. The bounded artifact validator/importer produces explicit
|
||||
immutable `IMPORTED_TRUSTED` calibrations for the existing known-calibration
|
||||
Sparse SfM contract. It is not self-calibration inside Sparse SfM and never
|
||||
makes EXIF a scientific calibration source. Solver execution remains outside this importer and is not
|
||||
claimed as implemented by this stage.
|
||||
|
||||
## Boundary and identity
|
||||
|
||||
Sparse SfM continues to consume an explicit Track Set and immutable calibration
|
||||
scope. Bootstrap artifacts can support a `Lardon3DSparseCalibration`; external
|
||||
poses, tracks, matches and points are diagnostics only and never become Lardon3D
|
||||
scientific reconstruction results. Each artifact identifies a calibration
|
||||
evidence run, not a Capture, Asset, `image_id`, path, basename, Task ID or
|
||||
external reconstruction. It records ordered representation hashes/dimensions,
|
||||
solver executable identity/model/configuration, initialization evidence,
|
||||
optimized parameters and validation diagnostics. Its SHA-256 is the immutable
|
||||
`IMPORTED_TRUSTED` provenance fingerprint.
|
||||
|
||||
## Artifact format v1
|
||||
|
||||
The import API borrows at most 600000 artifact bytes and requires their SHA-256 to equal an
|
||||
independently supplied expected digest before parsing. The format is fixed-width little-endian; binary64
|
||||
uses IEEE-754 bits, rejects non-finite values and canonicalizes negative zero before calibration
|
||||
publication. Native structs, padding, locale text and trailing bytes are never accepted.
|
||||
|
||||
```text
|
||||
magic 8 bytes = ASCII "L3DCALB1"
|
||||
format_version u32 = 1
|
||||
model_kind u32 = PINHOLE(1)
|
||||
model_version u32 = 1
|
||||
entry_count u32, 1..4096 and equal to selected item_count
|
||||
solver_executable_sha256 32 bytes, nonzero
|
||||
solver_configuration_sha256 32 bytes, nonzero
|
||||
initialization_evidence_sha256 32 bytes, nonzero
|
||||
validation_evidence_sha256 32 bytes, nonzero
|
||||
for each selected item in immutable item_index order:
|
||||
image_id u64
|
||||
representation_sha256 32 bytes
|
||||
width, height u32, u32
|
||||
fx, fy, cx, cy f64, f64, f64, f64
|
||||
k1, k2, p1, p2 f64, f64, f64, f64
|
||||
support_images u32, nonzero
|
||||
support_observations u32, nonzero
|
||||
reprojection_rmse_px finite nonnegative f64
|
||||
maximum_parameter_delta finite nonnegative f64
|
||||
validation_flags u32 = 0x0f
|
||||
```
|
||||
|
||||
The four v1 validation bits attest convergence, non-degenerate support, deterministic stability, and
|
||||
representative-coordinate equivalence to Lardon3D respectively. The importer requires all four; it does
|
||||
not silently derive missing evidence or invent a calibration threshold. `image_id` and representation
|
||||
SHA-256 must exactly match the durable selected item and its image asset. Width/height are artifact-owned
|
||||
geometry evidence and also enter calibration scientific identity. The artifact contains no external pose,
|
||||
point, track or match field, so those diagnostics cannot cross the import boundary.
|
||||
|
||||
## Exact model requirement
|
||||
|
||||
Bootstrap v1 accepts only an exact equivalent of Lardon3D's OpenCV-style
|
||||
pinhole model with binary64 `fx, fy, cx, cy, k1, k2, p1, p2`:
|
||||
|
||||
```text
|
||||
x_d = x * (1 + k1*r2 + k2*r2*r2) + 2*p1*x*y + p2*(r2 + 2*x*x)
|
||||
y_d = y * (1 + k1*r2 + k2*r2*r2) + p1*(r2 + 2*y*y) + 2*p2*x*y
|
||||
u = fx*x_d + cx; v = fy*y_d + cy
|
||||
```
|
||||
|
||||
The imported geometry is exactly the downstream Feature Store geometry. Models
|
||||
with active extra coefficients are rejected, never truncated. Representative
|
||||
coordinates must validate origin, axes, pixel convention and projection against
|
||||
the Lardon3D implementation before import.
|
||||
|
||||
## Metadata, representations and failure state
|
||||
|
||||
Metadata is initialization/grouping evidence only. Orientation does not identify
|
||||
a physical camera, though coordinates must match downstream decoding. A6000
|
||||
calibrates deterministic RAW-derived PNG geometry, never camera-JPEG geometry
|
||||
without a separately proven exact transform. Candidate A6000 groups use RAW
|
||||
geometry, lens evidence and exact EXIF focal only as solver inputs; zoom groups
|
||||
remain separate unless optimization proves equivalence. S21 uses its selected
|
||||
source JPEG geometry and solver-supported module grouping.
|
||||
|
||||
Bootstrap requires convergence, finite values, positive focal lengths, valid
|
||||
principal points, non-degenerate support, model compatibility and deterministic
|
||||
stability evidence. Unstable groups are expanded or partitioned; metadata
|
||||
interpolation and guessed calibration are prohibited. An affected selected image
|
||||
is `CALIBRATION_UNAVAILABLE`, not a quality rejection or corrupt source.
|
||||
|
||||
The real S21 and current A6000 Engine Bay selected campaigns are presently
|
||||
`CALIBRATION_UNAVAILABLE` because the available evidence is scientifically
|
||||
non-identifiable for the known-calibration contract. This is neither a source
|
||||
or quality failure nor a software failure. No pseudo-calibration, metadata
|
||||
interpolation or inferred import is permitted; real Sparse SfM is consequently
|
||||
`BLOCKED_BY_KNOWN_CALIBRATION_DATA`. The v22 normal suite passed 53/53, C17
|
||||
syntax checks and `git diff --check` passed, targeted ASan/UBSan validation
|
||||
passed, and final review passed. The full ASan/UBSan suite remains qualified by
|
||||
third-party RADV LSan behavior and is not represented as a repository-wide clean
|
||||
sanitizer pass. This bootstrap infrastructure is therefore `PASS / FROZEN`; that
|
||||
lifecycle state applies to the bounded importer and persistence infrastructure,
|
||||
not to calibration of either real campaign or to real Sparse SfM execution.
|
||||
|
||||
A dedicated future physical calibration acquisition, with images and evidence
|
||||
adequate to establish an exact supported model for each relevant representation
|
||||
group, is recommended before attempting real known-calibration Sparse SfM.
|
||||
That acquisition is future field work, not an implemented solver, a change to
|
||||
the scientific contract, or permission to retrofit the present campaigns.
|
||||
|
||||
## Execution relationship and resources
|
||||
|
||||
```text
|
||||
SCIENTIFIC_EXECUTABLE = QUALITY_SELECTED
|
||||
intersection REPRESENTATION_READY
|
||||
intersection CALIBRATION_ASSIGNED
|
||||
```
|
||||
|
||||
Quality selection is snapshotted immutably. A durable path must retain explicit
|
||||
quality-group → campaign-group → Capture mappings: equal numeric group values
|
||||
from independent Task requests imply no identity. Source photos remain read-only;
|
||||
RAW bootstrap development processes one image at a time, with no campaign-wide
|
||||
decoded cache. Ticket-owned artifacts retain source/representation hashes,
|
||||
output dimensions, policy fingerprint and solver provenance.
|
||||
|
||||
Project DB v22 stores the ordered selected rows and advances representation publication one item
|
||||
at a time. Each row immutably declares either a Capture-owned SOURCE RAW `asset_id` for S3-B1 or
|
||||
that the representation is a source image and no RAW identity exists. Creation validates
|
||||
`capture_source_assets.source_kind` as RAW; later execution never resolves it from path, digest, basename,
|
||||
request source index or group number. An `image_id` is accepted only through the retained Capture's explicit
|
||||
`capture_images` relation. The calibration scope is attached only after all representations are
|
||||
durable and only when that existing immutable scope contains every selected image. These DB
|
||||
transitions are idempotent exact retries; they do not execute a solver or RAW developer and do not
|
||||
introduce a scheduler, worker pool, sidecar, or parallel resource owner.
|
||||
|
||||
Artifact validation completes before any publication. Calibration and scope creation then reuse the
|
||||
existing short Project DB transactions. A crash or final attachment conflict may therefore retain only
|
||||
immutable content-addressed calibration evidence; it cannot make the selected execution `READY`. An exact
|
||||
retry reuses that evidence and converges through the existing scope attachment. The importer has no Task,
|
||||
Queue or Governor ownership and holds only two arrays bounded by the selected item count; solver execution
|
||||
and one-at-a-time RAW development must be coordinated by the established runtime when invoked.
|
||||
The thin durable `raw.develop` Task provides that S3-B1 execution boundary for one exact
|
||||
`capture_id`/`source_asset_id` pair. It reuses the existing single-active Queue and Governor, never
|
||||
advances the selected-execution cursor itself, and retains its published `image_id` before terminal
|
||||
generic Task progress can become durable. Its reservation owns one CPU thread, one I/O slot and a
|
||||
conservative 2 GiB working allowance for the bounded 40 MP decoder and publication buffers only for
|
||||
the callback lifetime; this operational admission bound is not a scientific Capture-count limit.
|
||||
A higher-level selected-execution coordinator remains separate scope.
|
||||
|
|
@ -1,5 +1,71 @@
|
|||
# Base de données projet Lardon3D
|
||||
|
||||
## Snapshot d'exécution scientifique sélectionnée — Project DB v22
|
||||
|
||||
**PASS / FROZEN.** La migration additive v21→v22 ajoute
|
||||
`capture_source_assets`, `selected_executions`, `selected_execution_items` et
|
||||
`raw_development_tasks`.
|
||||
`capture_source_assets` retient l'association explicitement publiée par S3-E entre un Capture,
|
||||
son asset `SOURCE` et le kind JPEG ou RAW déjà validé. La publication de l'association SOURCE et
|
||||
du kind est transactionnelle et un retry exact converge ; un autre kind est un conflit. Les
|
||||
Captures provenant d'une v21 restent honnêtement sans mapping : la migration ne déduit rien de
|
||||
leur chemin, nom, SHA-256, ordre d'attache ou image logique.
|
||||
|
||||
Un snapshot ordonné retient explicitement
|
||||
la relation `quality_task_id/group_id → campaign_task_id/group_id → capture_id`; les deux espaces
|
||||
de `group_id` restent indépendants et une égalité numérique n'est jamais une identité. Les deux
|
||||
tâches doivent viser le même ScanSet, le triage doit être terminé et chaque résultat retenu doit
|
||||
être effectivement inclus au moment du snapshot. Le snapshot et son ordre deviennent ensuite
|
||||
immuables.
|
||||
|
||||
Chaque item reçoit un `image_id` seulement par association explicite déjà durable dans
|
||||
`capture_images`. La publication de cette représentation et l'avancement du curseur sont
|
||||
transactionnels ; un retry exact converge, tandis qu'un autre `image_id` est un conflit. Après le
|
||||
dernier item, un `calibration_scope_id` existant ne peut être attaché que si chaque image retenue
|
||||
est membre de ce scope. Le stage terminal `READY` représente donc exactement l'intersection
|
||||
`QUALITY_SELECTED ∩ REPRESENTATION_READY ∩ CALIBRATION_ASSIGNED` sans inférer Capture depuis
|
||||
Asset, SHA-256, chemin, basename, Task ID, groupe ou `image_id`.
|
||||
|
||||
Le snapshot fixe aussi la source de représentation de chaque item. Un item A6000 retient
|
||||
explicitement l'`asset_id` RAW déjà associé comme `SOURCE` au Capture ; un item dont la
|
||||
représentation est une image source retient explicitement l'absence d'identité RAW. Cette paire
|
||||
discriminant/Asset est immuable et fait partie de l'exact retry du snapshot. La création vérifie
|
||||
l'association RAW dans `capture_source_assets` ; aucune exécution ultérieure ne retrouve le RAW depuis
|
||||
un chemin, SHA-256, basename, index source ou numéro de groupe.
|
||||
|
||||
Le curseur est borné à 4096 items et ne réserve aucune ressource d'exécution. Cette fondation v22
|
||||
n'ajoute ni scheduler, worker pool, sidecar, cache décodé, ni nouvelle Queue/Governor. Le
|
||||
coordinateur scientifique et le développement RAW borné restent des étapes d'exécution séparées.
|
||||
L'importeur Calibration Bootstrap v1 valide un artifact borné contre ce snapshot, crée/réutilise les
|
||||
calibrations et le scope immuables, puis emploie cette attache v22 ; il n'étend pas le schéma et n'exécute
|
||||
ni solveur ni développement RAW.
|
||||
|
||||
Le développeur RAW expose aussi une entrée bornée par `source_asset_id` explicite. Elle exige la
|
||||
relation RAW du Capture, charge le chemin géré depuis cet ID, vérifie les octets contre le SHA-256
|
||||
persisté, puis réutilise sans modification RAW Policy v1. Le chemin géré n'est donc qu'un accès aux
|
||||
octets après résolution d'identité explicite, jamais une méthode de découverte d'identité.
|
||||
|
||||
La v22 amendée ajoute `raw_development_tasks` pour l'exécution durable mince de cette entrée. Une
|
||||
ligne retient exactement `task_id`, `capture_id`, `source_asset_id`, une phase monotone
|
||||
`PENDING → PUBLISHED` et, en phase publiée, l'`image_id` dérivée explicitement associée au même
|
||||
Capture. Le couple Capture/SOURCE RAW est immuable, doit exister dans `capture_source_assets` avec
|
||||
le kind RAW, et n'est jamais retrouvé depuis un chemin, digest, nom, image ou ID opérationnel.
|
||||
Snapshot Task générique, référence du checkpoint et ligne typée sont écrits dans une même
|
||||
transaction. L'asset et l'image immuables peuvent précéder ce checkpoint si le processus meurt :
|
||||
le retry exact S3-B1 converge par contenu puis publie la phase et l'`image_id` sans deviner
|
||||
l'identité. Les lectures de reprise rejettent types SQLite, phases, nullabilité et relations
|
||||
Capture/RAW/image incohérents comme corruption durable.
|
||||
|
||||
La suite normale v22 a passé 53/53, les contrôles syntaxiques C17 et `git diff
|
||||
--check` ont passé, la validation ciblée ASan/UBSan a passé et l'audit final a
|
||||
conclu au PASS. La suite ASan/UBSan complète reste qualifiée par le comportement
|
||||
LSan du pilote tiers RADV ; elle ne constitue pas un PASS complet du dépôt.
|
||||
Cette évidence gèle la frontière de persistance v22 décrite ici, sans changement
|
||||
de contrat ni de schéma. Les campagnes réelles peuvent donc conserver un snapshot
|
||||
et leurs représentations sans être scientifiquement exécutables : une absence de
|
||||
calibration connue reste `CALIBRATION_UNAVAILABLE`, non une raison d'inférer ou
|
||||
de créer une identité, une calibration ou une migration supplémentaire.
|
||||
|
||||
## Photo Quality Triage — Project DB v21
|
||||
|
||||
**PASS / FROZEN.** La migration additive v20→v21 ajoute
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
**ACCEPTED** — architecture decision for the post-Gate C documentation freeze.
|
||||
|
||||
Current Sparse SfM gates A through G are **PASS / FROZEN**.
|
||||
Project Database: current schema **v20**; historical v16 remains frozen.
|
||||
Project Database: current schema **v22**; historical v16 remains frozen.
|
||||
|
||||
This record is normative for the current architecture. It does not introduce
|
||||
an implementation, a public API, a persistence format or a roadmap commitment.
|
||||
|
|
|
|||
|
|
@ -121,6 +121,26 @@ Queue/Governor, persistance tâche/groupe→Capture et reprise sans duplication.
|
|||
La capacité bornée des propositions de revue conserve un préfixe déterministe;
|
||||
elle ne limite jamais l'évaluation complète ni le groupement scientifique.
|
||||
|
||||
### État de calibration des campagnes réelles
|
||||
|
||||
L'infrastructure Project DB v22, exécution sélectionnée, `raw.develop` et
|
||||
Calibration Bootstrap v1 est **PASS / FROZEN** : la suite normale 53/53, les
|
||||
contrôles syntaxiques C17, `git diff --check`, la validation ciblée ASan/UBSan
|
||||
et l'audit final ont passé. La suite ASan/UBSan complète reste qualifiée par le
|
||||
comportement LSan du pilote tiers RADV ; elle n'est pas déclarée PASS complet du
|
||||
dépôt. Ce gel concerne l'infrastructure de persistance et d'import borné, pas
|
||||
la calibration des campagnes réelles ni leur Sparse SfM.
|
||||
|
||||
Les campagnes réelles S21 et A6000 Engine Bay actuellement évaluées sont
|
||||
`CALIBRATION_UNAVAILABLE` par non-identifiabilité scientifique des données
|
||||
disponibles pour le contrat Sparse SfM à calibration connue. Ce statut ne
|
||||
signifie ni échec logiciel, ni défaut des sources, ni rejet de qualité. Sans
|
||||
pseudo-calibration, interpolation de métadonnées ou import inféré, le Sparse
|
||||
SfM réel est `BLOCKED_BY_KNOWN_CALIBRATION_DATA`; l'intégration synthétique à
|
||||
calibration connue a passé. Une acquisition physique dédiée de calibration est
|
||||
une étape future, décrite dans [Calibration Bootstrap v1](../architecture/calibration_bootstrap.md),
|
||||
et non une fonctionnalité déjà réalisée.
|
||||
|
||||
## PHOTO QUALITY TRIAGE / ACQUISITION SELECTION — PASS / FROZEN
|
||||
|
||||
L'étape qualité canonique implémentée se place après la découverte bornée, les
|
||||
|
|
@ -484,8 +504,9 @@ L'ordre demeure sans ambiguïté :
|
|||
|
||||
```text
|
||||
CURRENT NEXT
|
||||
intégration réelle multi-campagne A6000 + S21
|
||||
→ pipeline scientifique → dense/mesh → publication
|
||||
acquisition physique dédiée de calibration
|
||||
→ calibration connue validée → Sparse SfM réel multi-campagne
|
||||
→ dense/mesh → publication
|
||||
→ LATER
|
||||
Coverage Analysis → Coverage Viewer → suggestions de points de vue
|
||||
→ localisation live → intégration HDMI/USB → Capture Guidance / Live Coverage
|
||||
|
|
|
|||
59
include/lardon3d/calibration_bootstrap.h
Normal file
59
include/lardon3d/calibration_bootstrap.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef LARDON3D_CALIBRATION_BOOTSTRAP_H
|
||||
#define LARDON3D_CALIBRATION_BOOTSTRAP_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lardon3d/project_db.h>
|
||||
#include <lardon3d/sparse_sfm_model.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum {
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_ARTIFACT_VERSION = 1,
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_MAX_BYTES = 600000,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_OK = 0,
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_INVALID_ARGUMENT,
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_MALFORMED_ARTIFACT,
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_PROVENANCE_MISMATCH,
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_SELECTION_CONFLICT,
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_DB_ERROR,
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_OUT_OF_MEMORY,
|
||||
} Lardon3DCalibrationBootstrapResult;
|
||||
|
||||
typedef struct {
|
||||
unsigned char artifact_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||
Lardon3DSparseCalibrationScope scope;
|
||||
uint32_t calibration_count;
|
||||
} Lardon3DCalibrationBootstrapOutput;
|
||||
|
||||
/* Import a complete Calibration Bootstrap v1 artifact for an existing selected
|
||||
* execution. All pointers are required and artifact bytes are borrowed only
|
||||
* for the call. The artifact is bounded by MAX_BYTES, uses the documented
|
||||
* fixed-width little-endian format, and must hash to expected_artifact_sha256.
|
||||
* Its ordered image IDs and representation SHA-256 values must exactly match
|
||||
* the execution's durable selected images. Only the exact OpenCV-compatible
|
||||
* eight-parameter pinhole model is accepted.
|
||||
*
|
||||
* Successful import creates/reuses immutable IMPORTED_TRUSTED calibrations,
|
||||
* creates/reuses their immutable scope, then attaches that scope through the
|
||||
* selected-execution contract. Exact retries converge. A failure after an
|
||||
* immutable calibration is created may retain that content-addressed evidence,
|
||||
* but cannot make the selected execution READY. The function imports no poses,
|
||||
* points, tracks, matches, EXIF calibration, or Capture identity. */
|
||||
Lardon3DCalibrationBootstrapResult lardon3d_calibration_bootstrap_import(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char expected_artifact_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
||||
Lardon3DCalibrationBootstrapOutput *output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -67,6 +67,12 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_acquisition_camp
|
|||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_photo_quality_task(
|
||||
Lardon3DAppState *state, const Lardon3DTask *task,
|
||||
const Lardon3DProjectDbPhotoQualityTask *parameters);
|
||||
/* Persist one RAW Task snapshot and its exact Capture/SOURCE RAW phase in the
|
||||
* same DB transaction. The checkpoint file is atomically published first; OK
|
||||
* means the file, generic row, checkpoint reference, and typed row are durable. */
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_raw_development_task(
|
||||
Lardon3DAppState *state, const Lardon3DTask *task,
|
||||
const Lardon3DProjectDbRawDevelopmentTask *parameters);
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_feature_extract_task(
|
||||
Lardon3DAppState *state, const Lardon3DTask *task,
|
||||
const Lardon3DProjectDbFeatureExtractTask *parameters);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include <lardon3d/sparse_sfm_incremental.h>
|
||||
|
||||
enum {
|
||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 21,
|
||||
LARDON3D_PROJECT_DB_SCHEMA_VERSION = 22,
|
||||
LARDON3D_PROJECT_DB_ID_CAPACITY = 65,
|
||||
LARDON3D_PROJECT_DB_KIND_CAPACITY = 65,
|
||||
LARDON3D_PROJECT_DB_PATH_CAPACITY = 4096,
|
||||
|
|
@ -126,6 +126,20 @@ typedef struct {
|
|||
size_t request_size;
|
||||
} Lardon3DProjectDbPhotoQualityTask;
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_RAW_DEVELOPMENT_TASK_PENDING = 1,
|
||||
LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED = 2,
|
||||
} Lardon3DProjectDbRawDevelopmentTaskPhase;
|
||||
|
||||
typedef struct {
|
||||
uint64_t task_id;
|
||||
uint64_t capture_id;
|
||||
uint64_t source_asset_id;
|
||||
Lardon3DProjectDbRawDevelopmentTaskPhase phase;
|
||||
bool has_image;
|
||||
uint64_t image_id;
|
||||
} Lardon3DProjectDbRawDevelopmentTask;
|
||||
|
||||
typedef struct {
|
||||
uint64_t task_id;
|
||||
uint32_t group_id;
|
||||
|
|
@ -134,6 +148,44 @@ typedef struct {
|
|||
Lardon3DPhotoQualityOverride override_value;
|
||||
} Lardon3DProjectDbPhotoQualityResult;
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_SELECTED_EXECUTION_REPRESENTATIONS = 1,
|
||||
LARDON3D_SELECTED_EXECUTION_CALIBRATION = 2,
|
||||
LARDON3D_SELECTED_EXECUTION_READY = 3,
|
||||
} Lardon3DProjectDbSelectedExecutionStage;
|
||||
|
||||
typedef struct {
|
||||
uint64_t execution_id;
|
||||
uint64_t quality_task_id;
|
||||
uint64_t campaign_task_id;
|
||||
bool has_calibration_scope;
|
||||
uint64_t calibration_scope_id;
|
||||
Lardon3DProjectDbSelectedExecutionStage stage;
|
||||
uint32_t next_item_index;
|
||||
uint32_t item_count;
|
||||
int64_t created_at;
|
||||
} Lardon3DProjectDbSelectedExecution;
|
||||
|
||||
typedef enum {
|
||||
/* The selected representation is an existing source image; no RAW
|
||||
* development identity is present or may be inferred later. */
|
||||
LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE = 1,
|
||||
/* source_asset_id explicitly identifies the Capture-owned SOURCE RAW whose
|
||||
* deterministic S3-B1 representation is required. */
|
||||
LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET = 2,
|
||||
} Lardon3DProjectDbSelectedRepresentationSource;
|
||||
|
||||
typedef struct {
|
||||
uint32_t item_index;
|
||||
uint32_t quality_group_id;
|
||||
uint32_t campaign_group_id;
|
||||
uint64_t capture_id;
|
||||
Lardon3DProjectDbSelectedRepresentationSource representation_source;
|
||||
uint64_t source_asset_id;
|
||||
bool has_image;
|
||||
uint64_t image_id;
|
||||
} Lardon3DProjectDbSelectedExecutionItem;
|
||||
|
||||
typedef struct {
|
||||
uint64_t scanset_id;
|
||||
char name[LARDON3D_PROJECT_DB_SCANSET_NAME_CAPACITY];
|
||||
|
|
@ -180,6 +232,17 @@ typedef struct {
|
|||
Lardon3DProjectDbCaptureAssetRole role;
|
||||
} Lardon3DProjectDbCaptureAsset;
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_DB_CAPTURE_SOURCE_JPEG = 1,
|
||||
LARDON3D_DB_CAPTURE_SOURCE_RAW = 2,
|
||||
} Lardon3DProjectDbCaptureSourceKind;
|
||||
|
||||
typedef struct {
|
||||
uint64_t capture_id;
|
||||
uint64_t asset_id;
|
||||
Lardon3DProjectDbCaptureSourceKind source_kind;
|
||||
} Lardon3DProjectDbCaptureSourceAsset;
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED = 1,
|
||||
} Lardon3DProjectDbAssetDerivationKind;
|
||||
|
|
@ -539,6 +602,11 @@ Lardon3DProjectDbResult lardon3d_project_db_register_image_asset(
|
|||
Lardon3DProjectDb *database,
|
||||
const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path,
|
||||
uint64_t size_bytes, int64_t created_at, Lardon3DProjectDbImageAsset *asset);
|
||||
/* Load one immutable managed asset by its explicit durable ID. The caller owns
|
||||
* output storage. The returned relative path is validated and NUL-terminated;
|
||||
* this API performs no Capture/source identity discovery. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_image_asset(
|
||||
Lardon3DProjectDb *database, uint64_t asset_id, Lardon3DProjectDbImageAsset *asset);
|
||||
Lardon3DProjectDbResult lardon3d_project_db_publish_derived_capture_image(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
|
||||
const char *original_name, const char *source_path, uint64_t producer_task_id,
|
||||
|
|
@ -574,6 +642,18 @@ Lardon3DProjectDbResult lardon3d_project_db_attach_capture_asset(
|
|||
Lardon3DProjectDbCaptureAssetRole role);
|
||||
Lardon3DProjectDbResult lardon3d_project_db_attach_capture_source_asset(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id);
|
||||
/* Atomically attach an existing asset as SOURCE and retain the explicit source
|
||||
* kind observed by S3-E. Exact retry is idempotent; a different kind conflicts.
|
||||
* This provenance must never be synthesized from path, name, SHA-256,
|
||||
* attachment order, or image identity. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_capture_source_asset(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
|
||||
Lardon3DProjectDbCaptureSourceKind source_kind);
|
||||
/* Lists only explicitly retained source-kind relations. Captures migrated from
|
||||
* v21 may truthfully return an empty page; absence is not permission to infer. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_list_capture_source_assets(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t after_asset_id,
|
||||
Lardon3DProjectDbCaptureSourceAsset *assets, size_t capacity, size_t *count);
|
||||
Lardon3DProjectDbResult lardon3d_project_db_list_capture_assets(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t after_asset_id,
|
||||
Lardon3DProjectDbCaptureAsset *assets, size_t capacity, size_t *count);
|
||||
|
|
@ -633,6 +713,30 @@ Lardon3DProjectDbResult lardon3d_project_db_record_photo_quality_task(
|
|||
Lardon3DProjectDbResult lardon3d_project_db_load_photo_quality_task(
|
||||
Lardon3DProjectDb *database, uint64_t task_id, unsigned char *request,
|
||||
size_t request_capacity, Lardon3DProjectDbPhotoQualityTask *parameters);
|
||||
/* Atomically persist generic Task/checkpoint state with one immutable explicit
|
||||
* Capture/SOURCE RAW identity and its monotonic publication phase. The caller
|
||||
* must pass `task_kind` == `LARDON3D_RAW_DEVELOPMENT_TASK_KIND` and
|
||||
* `task_kind_version` == `LARDON3D_RAW_DEVELOPMENT_TASK_KIND_VERSION`.
|
||||
* Any mismatch is rejected with INVALID_ARGUMENT because generic Task dispatch and
|
||||
* typed durable payload must remain coherent across restart. PUBLISHED requires
|
||||
* the exact derived image retained by that Capture; exact retries are idempotent
|
||||
* and another identity or image conflicts. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_raw_development_task(
|
||||
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
const char *task_kind, uint32_t task_kind_version,
|
||||
const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||
const Lardon3DProjectDbRawDevelopmentTask *parameters, int64_t updated_at);
|
||||
/* Load and validate one durable RAW Task. Persisted durable rows with
|
||||
* `task_kind`/`task_kind_version` not equal to
|
||||
* `LARDON3D_RAW_DEVELOPMENT_TASK_KIND`/`LARDON3D_RAW_DEVELOPMENT_TASK_KIND_VERSION`
|
||||
* are corrupted durable state and return CORRUPT, preserving the contract that
|
||||
* typed durable payload and generic Task identity never silently diverge.
|
||||
* Invalid phases, missing explicit RAW provenance, or a published image outside
|
||||
* the retained Capture are also reported as corrupt durable state; no source
|
||||
* identity is inferred. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_raw_development_task(
|
||||
Lardon3DProjectDb *database, uint64_t task_id,
|
||||
Lardon3DProjectDbRawDevelopmentTask *parameters);
|
||||
/* Result publication and next_group_id advance are atomic. result->group_id is
|
||||
* the canonical plan ID in 1..N and must equal the task's current one-based
|
||||
* cursor; next_group_id must equal result->group_id+1 (N+1 after the last
|
||||
|
|
@ -652,6 +756,44 @@ Lardon3DProjectDbResult lardon3d_project_db_load_photo_quality_result(
|
|||
Lardon3DProjectDbResult lardon3d_project_db_set_photo_quality_override(
|
||||
Lardon3DProjectDb *database, uint64_t task_id, uint32_t group_id,
|
||||
Lardon3DPhotoQualityOverride override_value);
|
||||
/* Create an immutable, ordered QUALITY_SELECTED snapshot. Each item explicitly
|
||||
* bridges independent quality and campaign group namespaces to a retained
|
||||
* Capture; equal numeric group values never imply identity. Each item also
|
||||
* declares either an explicit Capture-owned SOURCE RAW asset or that its
|
||||
* representation is a source image and therefore has no RAW identity. The DB
|
||||
* validates RAW asset membership at creation and never recovers it from path,
|
||||
* SHA-256, basename, source index, or group number. The caller-owned item array
|
||||
* is borrowed only for the call. Every referenced quality result must be
|
||||
* effectively included and both tasks must belong to the same ScanSet. An
|
||||
* exact retry returns the existing execution; a changed mapping conflicts. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_create_selected_execution(
|
||||
Lardon3DProjectDb *database, uint64_t quality_task_id,
|
||||
uint64_t campaign_task_id, const Lardon3DProjectDbSelectedExecutionItem *items,
|
||||
size_t item_count, int64_t created_at,
|
||||
Lardon3DProjectDbSelectedExecution *execution);
|
||||
/* Load durable stage/cursor state. Output is caller-owned and contains no
|
||||
* borrowed storage. Corrupt SQLite types, signs, bounds, or stage/cursor
|
||||
* relations are rejected before narrowing to the public C17 fields. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_selected_execution(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
Lardon3DProjectDbSelectedExecution *execution);
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_selected_execution_item(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id, uint32_t item_index,
|
||||
Lardon3DProjectDbSelectedExecutionItem *item);
|
||||
/* Publish one representation mapping and advance the cursor atomically. The
|
||||
* image must already be explicitly associated with this item's Capture.
|
||||
* Retrying the exact mapping after cursor advance is idempotent; no SHA, path,
|
||||
* Asset, basename, or group number is used to recover identity. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_selected_representation(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id, uint32_t item_index,
|
||||
uint64_t image_id, uint32_t next_item_index);
|
||||
/* Attach an existing immutable calibration scope only after all selected
|
||||
* representations are durable and every selected image is a member. The
|
||||
* successful transition makes the snapshot scientifically executable; an
|
||||
* exact scope retry is idempotent and a different scope conflicts. */
|
||||
Lardon3DProjectDbResult lardon3d_project_db_assign_selected_calibration_scope(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
uint64_t calibration_scope_id);
|
||||
Lardon3DProjectDbResult lardon3d_project_db_allocate_task_id(Lardon3DProjectDb *database,
|
||||
uint64_t *task_id);
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_task(Lardon3DProjectDb *database, uint64_t task_id,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,16 @@ Lardon3DRawDevelopmentResult lardon3d_raw_develop_to_capture(
|
|||
uint64_t producer_task_id, int64_t created_at, Lardon3DRawDevelopmentOutput *output
|
||||
);
|
||||
|
||||
/* Develop the immutable managed RAW identified explicitly by source_asset_id.
|
||||
* The asset must have a durable RAW source-kind relation to capture_id. This
|
||||
* API validates the managed bytes against their stored SHA-256 and never finds
|
||||
* source identity from path, name, hash, attachment order, or image identity.
|
||||
* Output storage is caller-owned; published assets/images remain DB-owned. */
|
||||
Lardon3DRawDevelopmentResult lardon3d_raw_develop_asset_to_capture(
|
||||
Lardon3DAppState *state, uint64_t capture_id, uint64_t source_asset_id,
|
||||
uint64_t producer_task_id, int64_t created_at, Lardon3DRawDevelopmentOutput *output
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
44
include/lardon3d/raw_development_task.h
Normal file
44
include/lardon3d/raw_development_task.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef LARDON3D_RAW_DEVELOPMENT_TASK_H
|
||||
#define LARDON3D_RAW_DEVELOPMENT_TASK_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lardon3d/app_state.h>
|
||||
#include <lardon3d/task_kind_registry.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define LARDON3D_RAW_DEVELOPMENT_TASK_KIND "raw.develop"
|
||||
enum { LARDON3D_RAW_DEVELOPMENT_TASK_KIND_VERSION = 1 };
|
||||
|
||||
/* Create one durable, initially unqueued S3-B1 Task for the exact existing
|
||||
* Capture-owned SOURCE RAW asset. The two IDs are immutable and are never
|
||||
* inferred from paths, hashes, names, images, or Task identity. task_id is
|
||||
* required and receives the durable operational ID. The caller owns the
|
||||
* returned Task and must enqueue or destroy it. */
|
||||
Lardon3DTask *lardon3d_project_create_raw_development_task(
|
||||
Lardon3DAppState *state, uint64_t capture_id, uint64_t source_asset_id,
|
||||
uint64_t *task_id);
|
||||
|
||||
/* Create and transfer one durable RAW Task to state's existing Queue. Queue
|
||||
* admission owns the Task's bounded CPU/RAM/I/O reservation until callback
|
||||
* completion, failure, or cancellation; no worker or scheduler is created. */
|
||||
bool lardon3d_project_enqueue_raw_development(
|
||||
Lardon3DAppState *state, uint64_t capture_id, uint64_t source_asset_id,
|
||||
uint64_t *task_id);
|
||||
|
||||
/* Reconstruct callback state only from the validated typed v22 row. snapshot,
|
||||
* reconstruction context, and binding are required and caller-owned. The
|
||||
* binding owns its returned context until the runtime invokes its destroyer. */
|
||||
bool lardon3d_raw_development_task_reconstruct(
|
||||
const Lardon3DTaskDurableSnapshot *snapshot, void *context,
|
||||
Lardon3DTaskKindBinding *binding);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
53
meson.build
53
meson.build
|
|
@ -149,6 +149,7 @@ executable(
|
|||
'src/image_catalog.c',
|
||||
'src/image_catalog_persistent.c',
|
||||
'src/raw_development.cpp',
|
||||
'src/raw_development_task.cpp',
|
||||
'src/acquisition_pairing.cpp',
|
||||
'src/acquisition_ingest.cpp',
|
||||
'src/acquisition_campaign.cpp',
|
||||
|
|
@ -168,6 +169,7 @@ executable(
|
|||
'src/image_view.c',
|
||||
'src/project.c',
|
||||
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||
'src/calibration_bootstrap.c',
|
||||
'src/sparse_sfm_geometry.cpp',
|
||||
'src/sparse_sfm_incremental.cpp',
|
||||
'src/sparse_sfm_bundle_adjustment.cpp',
|
||||
|
|
@ -200,6 +202,7 @@ executable(
|
|||
'-DLARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_AVAILABLE',
|
||||
'-DLARDON3D_ACQUISITION_CAMPAIGN_TASK_AVAILABLE',
|
||||
'-DLARDON3D_PHOTO_QUALITY_TASK_AVAILABLE',
|
||||
'-DLARDON3D_RAW_DEVELOPMENT_TASK_AVAILABLE',
|
||||
],
|
||||
dependencies: [
|
||||
ncursesw,
|
||||
|
|
@ -330,6 +333,27 @@ raw_development_test = executable(
|
|||
|
||||
test('raw-development', raw_development_test, timeout: 30)
|
||||
|
||||
raw_development_task_test = executable(
|
||||
'test-raw-development-task',
|
||||
sources: [
|
||||
'tests/test_raw_development_task.cpp',
|
||||
'src/app_state.c',
|
||||
'src/image_catalog.c', 'src/image_view.c',
|
||||
'src/image_catalog_persistent.c',
|
||||
'src/raw_development.cpp',
|
||||
'src/raw_development_task.cpp',
|
||||
'src/project.c',
|
||||
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||
'src/task.c', 'src/task_checkpoint.c', 'src/task_queue.c',
|
||||
'src/task_kind_registry.c',
|
||||
'src/resource_governor.c', 'src/resource_snapshot.c',
|
||||
],
|
||||
include_directories: include_directories('include'),
|
||||
dependencies: [threads, sqlite3, openssl, opencv, libraw, libpng, libdeflate],
|
||||
)
|
||||
|
||||
test('raw-development-task', raw_development_task_test, timeout: 30)
|
||||
|
||||
acquisition_pairing_test = executable(
|
||||
'test-acquisition-pairing',
|
||||
sources: ['tests/test_acquisition_pairing.cpp', 'src/acquisition_pairing.cpp'],
|
||||
|
|
@ -712,6 +736,35 @@ project_db_test = executable(
|
|||
|
||||
test('project-db', project_db_test, timeout: 30)
|
||||
|
||||
selected_execution_test = executable(
|
||||
'test-selected-execution',
|
||||
sources: [
|
||||
'tests/test_selected_execution.c',
|
||||
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||
'src/task.c', 'src/resource_governor.c', 'src/resource_snapshot.c',
|
||||
],
|
||||
c_args: ['-DLARDON3D_PROJECT_DB_TESTING'],
|
||||
include_directories: include_directories('include'),
|
||||
dependencies: [threads, sqlite3, openssl],
|
||||
)
|
||||
|
||||
test('selected-execution', selected_execution_test, timeout: 30)
|
||||
|
||||
calibration_bootstrap_test = executable(
|
||||
'test-calibration-bootstrap',
|
||||
sources: [
|
||||
'tests/test_calibration_bootstrap.c',
|
||||
'src/calibration_bootstrap.c',
|
||||
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||
'src/task.c', 'src/resource_governor.c', 'src/resource_snapshot.c',
|
||||
],
|
||||
c_args: ['-DLARDON3D_PROJECT_DB_TESTING'],
|
||||
include_directories: include_directories('include'),
|
||||
dependencies: [threads, sqlite3, openssl],
|
||||
)
|
||||
|
||||
test('calibration-bootstrap', calibration_bootstrap_test, timeout: 30)
|
||||
|
||||
sparse_sfm_model_test = executable(
|
||||
'test-sparse-sfm-model',
|
||||
sources: [
|
||||
|
|
|
|||
|
|
@ -158,8 +158,18 @@ extern "C" Lardon3DAcquisitionIngestResult lardon3d_acquisition_ingest(
|
|||
size_t jpeg = SIZE_MAX, raw = SIZE_MAX;
|
||||
for (size_t member = 0; member < group.source_count; ++member) {
|
||||
size_t index = group.source_indices[member];
|
||||
database_result = lardon3d_project_db_attach_capture_source_asset(
|
||||
state->project_db, capture.capture_id, published[index].asset.asset_id);
|
||||
Lardon3DProjectDbCaptureSourceKind source_kind{};
|
||||
if (published[index].metadata.source_kind == LARDON3D_ACQUISITION_SOURCE_JPEG)
|
||||
source_kind = LARDON3D_DB_CAPTURE_SOURCE_JPEG;
|
||||
else if (published[index].metadata.source_kind == LARDON3D_ACQUISITION_SOURCE_RAW)
|
||||
source_kind = LARDON3D_DB_CAPTURE_SOURCE_RAW;
|
||||
else
|
||||
return LARDON3D_ACQUISITION_INGEST_CONSTRAINT;
|
||||
// S3-E commits SOURCE membership with the already validated source kind.
|
||||
// Downstream RAW work may use only this explicit relation; path, hash,
|
||||
// basename, attachment order, and image identity cannot replace it.
|
||||
database_result = lardon3d_project_db_record_capture_source_asset(
|
||||
state->project_db, capture.capture_id, published[index].asset.asset_id, source_kind);
|
||||
if (database_result != LARDON3D_PROJECT_DB_OK) return db_result(database_result);
|
||||
if (published[index].metadata.source_kind == LARDON3D_ACQUISITION_SOURCE_JPEG && jpeg == SIZE_MAX)
|
||||
jpeg = index;
|
||||
|
|
|
|||
220
src/calibration_bootstrap.c
Normal file
220
src/calibration_bootstrap.c
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
#include <lardon3d/calibration_bootstrap.h>
|
||||
|
||||
#include <math.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
enum {
|
||||
BOOTSTRAP_HEADER_SIZE = 152,
|
||||
BOOTSTRAP_ENTRY_SIZE = 140,
|
||||
BOOTSTRAP_VALIDATION_FLAGS = 15,
|
||||
};
|
||||
|
||||
static const unsigned char bootstrap_magic[8] = {'L', '3', 'D', 'C', 'A', 'L', 'B', '1'};
|
||||
|
||||
typedef struct {
|
||||
const unsigned char *bytes;
|
||||
size_t remaining;
|
||||
} BootstrapReader;
|
||||
|
||||
static bool take(BootstrapReader *reader, void *output, size_t count) {
|
||||
if (count > reader->remaining) return false;
|
||||
if (output) memcpy(output, reader->bytes, count);
|
||||
reader->bytes += count;
|
||||
reader->remaining -= count;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_u32(BootstrapReader *reader, uint32_t *output) {
|
||||
unsigned char bytes[4];
|
||||
if (!take(reader, bytes, sizeof(bytes))) return false;
|
||||
*output = (uint32_t)bytes[0] | ((uint32_t)bytes[1] << 8) |
|
||||
((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_u64(BootstrapReader *reader, uint64_t *output) {
|
||||
unsigned char bytes[8];
|
||||
if (!take(reader, bytes, sizeof(bytes))) return false;
|
||||
*output = 0;
|
||||
for (size_t index = 0; index < sizeof(bytes); ++index)
|
||||
*output |= (uint64_t)bytes[index] << (8u * index);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_f64(BootstrapReader *reader, double *output) {
|
||||
uint64_t bits;
|
||||
if (!read_u64(reader, &bits)) return false;
|
||||
memcpy(output, &bits, sizeof(bits));
|
||||
if (*output == 0.0) *output = 0.0;
|
||||
return isfinite(*output);
|
||||
}
|
||||
|
||||
static bool sha256(const unsigned char *bytes, size_t size, unsigned char output[32]) {
|
||||
unsigned int output_size = 0;
|
||||
return EVP_Digest(bytes, size, output, &output_size, EVP_sha256(), NULL) == 1 &&
|
||||
output_size == 32;
|
||||
}
|
||||
|
||||
static Lardon3DCalibrationBootstrapResult db_result(Lardon3DProjectDbResult result) {
|
||||
if (result == LARDON3D_PROJECT_DB_CONSTRAINT || result == LARDON3D_PROJECT_DB_NOT_FOUND)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_SELECTION_CONFLICT;
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_DB_ERROR;
|
||||
}
|
||||
|
||||
Lardon3DCalibrationBootstrapResult lardon3d_calibration_bootstrap_import(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char expected_artifact_sha256[32],
|
||||
Lardon3DCalibrationBootstrapOutput *output) {
|
||||
if (!database || execution_id == 0 || !artifact || !expected_artifact_sha256 || !output ||
|
||||
artifact_size < BOOTSTRAP_HEADER_SIZE ||
|
||||
artifact_size > LARDON3D_CALIBRATION_BOOTSTRAP_MAX_BYTES)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_INVALID_ARGUMENT;
|
||||
memset(output, 0, sizeof(*output));
|
||||
|
||||
unsigned char artifact_sha256[32];
|
||||
if (!sha256(artifact, artifact_size, artifact_sha256))
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_DB_ERROR;
|
||||
if (memcmp(artifact_sha256, expected_artifact_sha256, 32) != 0)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_PROVENANCE_MISMATCH;
|
||||
|
||||
Lardon3DProjectDbSelectedExecution execution;
|
||||
Lardon3DProjectDbResult loaded =
|
||||
lardon3d_project_db_load_selected_execution(database, execution_id, &execution);
|
||||
if (loaded != LARDON3D_PROJECT_DB_OK) return db_result(loaded);
|
||||
if (execution.stage != LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
|
||||
execution.stage != LARDON3D_SELECTED_EXECUTION_READY)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_SELECTION_CONFLICT;
|
||||
|
||||
BootstrapReader reader = {artifact, artifact_size};
|
||||
unsigned char magic[8];
|
||||
uint32_t version, model_kind, model_version, entry_count;
|
||||
unsigned char solver_executable_sha256[32];
|
||||
unsigned char solver_configuration_sha256[32];
|
||||
unsigned char initialization_evidence_sha256[32];
|
||||
unsigned char validation_evidence_sha256[32];
|
||||
if (!take(&reader, magic, sizeof(magic)) ||
|
||||
!read_u32(&reader, &version) || !read_u32(&reader, &model_kind) ||
|
||||
!read_u32(&reader, &model_version) || !read_u32(&reader, &entry_count) ||
|
||||
!take(&reader, solver_executable_sha256, 32) ||
|
||||
!take(&reader, solver_configuration_sha256, 32) ||
|
||||
!take(&reader, initialization_evidence_sha256, 32) ||
|
||||
!take(&reader, validation_evidence_sha256, 32) ||
|
||||
memcmp(magic, bootstrap_magic, sizeof(magic)) != 0 ||
|
||||
version != LARDON3D_CALIBRATION_BOOTSTRAP_ARTIFACT_VERSION ||
|
||||
model_kind != LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE ||
|
||||
model_version != LARDON3D_SPARSE_SFM_CALIBRATION_VERSION || entry_count == 0 ||
|
||||
entry_count != execution.item_count ||
|
||||
artifact_size != BOOTSTRAP_HEADER_SIZE + (size_t)entry_count * BOOTSTRAP_ENTRY_SIZE)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_MALFORMED_ARTIFACT;
|
||||
|
||||
/* These four digests bind executable identity, exact solver configuration,
|
||||
* initialization evidence and validation diagnostics into artifact identity.
|
||||
* An all-zero digest is not evidence and is therefore rejected. */
|
||||
unsigned char executable_or = 0, configuration_or = 0, initialization_or = 0, validation_or = 0;
|
||||
for (size_t index = 0; index < 32; ++index) {
|
||||
executable_or |= solver_executable_sha256[index];
|
||||
configuration_or |= solver_configuration_sha256[index];
|
||||
initialization_or |= initialization_evidence_sha256[index];
|
||||
validation_or |= validation_evidence_sha256[index];
|
||||
}
|
||||
if (executable_or == 0 || configuration_or == 0 || initialization_or == 0 ||
|
||||
validation_or == 0)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_MALFORMED_ARTIFACT;
|
||||
|
||||
Lardon3DSparseCalibration *calibrations = calloc(entry_count, sizeof(*calibrations));
|
||||
Lardon3DSparseCalibrationMember *members = calloc(entry_count, sizeof(*members));
|
||||
if (!calibrations || !members) {
|
||||
free(calibrations);
|
||||
free(members);
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
Lardon3DCalibrationBootstrapResult result = LARDON3D_CALIBRATION_BOOTSTRAP_OK;
|
||||
for (uint32_t index = 0; index < entry_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_OK;
|
||||
++index) {
|
||||
uint64_t image_id;
|
||||
unsigned char representation_sha256[32];
|
||||
uint32_t width, height, support_images, support_observations, validation_flags;
|
||||
double reprojection_rmse, maximum_parameter_delta;
|
||||
Lardon3DSparseCalibration *calibration = &calibrations[index];
|
||||
if (!read_u64(&reader, &image_id) || !take(&reader, representation_sha256, 32) ||
|
||||
!read_u32(&reader, &width) || !read_u32(&reader, &height) ||
|
||||
!read_f64(&reader, &calibration->fx) || !read_f64(&reader, &calibration->fy) ||
|
||||
!read_f64(&reader, &calibration->cx) || !read_f64(&reader, &calibration->cy) ||
|
||||
!read_f64(&reader, &calibration->k1) || !read_f64(&reader, &calibration->k2) ||
|
||||
!read_f64(&reader, &calibration->p1) || !read_f64(&reader, &calibration->p2) ||
|
||||
!read_u32(&reader, &support_images) || !read_u32(&reader, &support_observations) ||
|
||||
!read_f64(&reader, &reprojection_rmse) ||
|
||||
!read_f64(&reader, &maximum_parameter_delta) ||
|
||||
!read_u32(&reader, &validation_flags) || image_id == 0 || width == 0 || height == 0 ||
|
||||
calibration->fx <= 0.0 || calibration->fy <= 0.0 || calibration->cx < 0.0 ||
|
||||
calibration->cy < 0.0 || calibration->cx >= (double)width ||
|
||||
calibration->cy >= (double)height || support_images == 0 || support_observations == 0 ||
|
||||
reprojection_rmse < 0.0 || maximum_parameter_delta < 0.0 ||
|
||||
validation_flags != BOOTSTRAP_VALIDATION_FLAGS) {
|
||||
result = LARDON3D_CALIBRATION_BOOTSTRAP_MALFORMED_ARTIFACT;
|
||||
break;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbSelectedExecutionItem selected;
|
||||
Lardon3DProjectDbImage image;
|
||||
Lardon3DProjectDbImageAsset asset;
|
||||
loaded = lardon3d_project_db_load_selected_execution_item(database, execution_id, index,
|
||||
&selected);
|
||||
if (loaded != LARDON3D_PROJECT_DB_OK || !selected.has_image || selected.image_id != image_id) {
|
||||
result = LARDON3D_CALIBRATION_BOOTSTRAP_SELECTION_CONFLICT;
|
||||
break;
|
||||
}
|
||||
loaded = lardon3d_project_db_load_image(database, image_id, &image, &asset);
|
||||
if (loaded != LARDON3D_PROJECT_DB_OK || memcmp(asset.sha256, representation_sha256, 32) != 0) {
|
||||
result = LARDON3D_CALIBRATION_BOOTSTRAP_SELECTION_CONFLICT;
|
||||
break;
|
||||
}
|
||||
calibration->model_kind = model_kind;
|
||||
calibration->model_version = model_version;
|
||||
calibration->width = width;
|
||||
calibration->height = height;
|
||||
calibration->provenance_kind = LARDON3D_SPARSE_SFM_PROVENANCE_IMPORTED_TRUSTED;
|
||||
memcpy(calibration->provenance_fingerprint, artifact_sha256, 32);
|
||||
members[index].image_id = image_id;
|
||||
}
|
||||
if (reader.remaining != 0 && result == LARDON3D_CALIBRATION_BOOTSTRAP_OK)
|
||||
result = LARDON3D_CALIBRATION_BOOTSTRAP_MALFORMED_ARTIFACT;
|
||||
|
||||
/* Validation precedes publication. The established calibration APIs own
|
||||
* short transactions; their immutable, content-addressed rows may survive a
|
||||
* later failure, while READY remains guarded by the final scope attachment. */
|
||||
for (uint32_t index = 0; index < entry_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_OK;
|
||||
++index) {
|
||||
Lardon3DSparseCalibration stored;
|
||||
loaded = lardon3d_sparse_calibration_create(database, &calibrations[index], &stored);
|
||||
if (loaded != LARDON3D_PROJECT_DB_OK) {
|
||||
result = db_result(loaded);
|
||||
break;
|
||||
}
|
||||
members[index].calibration_id = stored.calibration_id;
|
||||
memcpy(members[index].calibration_hash, stored.scientific_hash, 32);
|
||||
}
|
||||
Lardon3DSparseCalibrationScope scope;
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_OK) {
|
||||
loaded = lardon3d_sparse_calibration_scope_create(database, members, entry_count, &scope);
|
||||
if (loaded != LARDON3D_PROJECT_DB_OK) result = db_result(loaded);
|
||||
}
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_OK) {
|
||||
loaded = lardon3d_project_db_assign_selected_calibration_scope(database, execution_id,
|
||||
scope.scope_id);
|
||||
if (loaded != LARDON3D_PROJECT_DB_OK) result = db_result(loaded);
|
||||
}
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_OK) {
|
||||
memcpy(output->artifact_sha256, artifact_sha256, 32);
|
||||
output->scope = scope;
|
||||
output->calibration_count = entry_count;
|
||||
}
|
||||
free(calibrations);
|
||||
free(members);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -676,7 +676,8 @@ checkpoint_task_internal(Lardon3DAppState *state, const Lardon3DTask *task,
|
|||
const Lardon3DProjectDbMatcherTask *matcher_parameters,
|
||||
const Lardon3DProjectDbGeometricVerifierTask *geometric_parameters,
|
||||
const Lardon3DProjectDbAcquisitionCampaignTask *campaign_parameters,
|
||||
const Lardon3DProjectDbPhotoQualityTask *quality_parameters) {
|
||||
const Lardon3DProjectDbPhotoQualityTask *quality_parameters,
|
||||
const Lardon3DProjectDbRawDevelopmentTask *raw_parameters) {
|
||||
if (!state || !state->project_loaded || !state->project_db) {
|
||||
return LARDON3D_PROJECT_TASK_CHECKPOINT_NO_PROJECT;
|
||||
}
|
||||
|
|
@ -751,6 +752,10 @@ checkpoint_task_internal(Lardon3DAppState *state, const Lardon3DTask *task,
|
|||
? lardon3d_project_db_record_photo_quality_task(
|
||||
state->project_db, &snapshot, task_kind, task_kind_version, &checkpoint,
|
||||
quality_parameters, now.tv_sec)
|
||||
: raw_parameters
|
||||
? lardon3d_project_db_record_raw_development_task(
|
||||
state->project_db, &snapshot, task_kind, task_kind_version, &checkpoint,
|
||||
raw_parameters, now.tv_sec)
|
||||
: lardon3d_project_db_record_task(state->project_db, &snapshot, task_kind,
|
||||
task_kind_version, &checkpoint, now.tv_sec);
|
||||
if (recorded == LARDON3D_PROJECT_DB_BUSY) {
|
||||
|
|
@ -767,7 +772,7 @@ checkpoint_task_internal(Lardon3DAppState *state, const Lardon3DTask *task,
|
|||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_task(Lardon3DAppState *state,
|
||||
const Lardon3DTask *task) {
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL);
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult
|
||||
|
|
@ -777,7 +782,7 @@ lardon3d_project_checkpoint_image_import_task(Lardon3DAppState *state, const Lar
|
|||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
return checkpoint_task_internal(
|
||||
state, task, source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
state, task, source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_acquisition_campaign_task(
|
||||
|
|
@ -785,7 +790,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_acquisition_camp
|
|||
const Lardon3DProjectDbAcquisitionCampaignTask *parameters) {
|
||||
if (!parameters) return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
parameters, NULL);
|
||||
parameters, NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_photo_quality_task(
|
||||
|
|
@ -793,7 +798,15 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_photo_quality_ta
|
|||
const Lardon3DProjectDbPhotoQualityTask *parameters) {
|
||||
if (!parameters) return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
parameters);
|
||||
parameters, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_raw_development_task(
|
||||
Lardon3DAppState *state, const Lardon3DTask *task,
|
||||
const Lardon3DProjectDbRawDevelopmentTask *parameters) {
|
||||
if (!parameters) return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, parameters);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_feature_extract_task(
|
||||
|
|
@ -803,7 +816,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_feature_extract_
|
|||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
return checkpoint_task_internal(state, task, NULL, 0, parameters, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL);
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_sift_extract_task(
|
||||
|
|
@ -811,7 +824,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_sift_extract_tas
|
|||
const Lardon3DProjectDbSiftExtractTask *parameters) {
|
||||
if (!parameters) return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, parameters, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL);
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_visual_index_update_task(
|
||||
|
|
@ -821,7 +834,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_visual_index_upd
|
|||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, parameters, NULL, NULL, NULL,
|
||||
NULL, NULL);
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_candidate_pair_generate_task(
|
||||
|
|
@ -831,7 +844,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_candidate_pair_g
|
|||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, parameters, NULL, NULL,
|
||||
NULL, NULL);
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_matcher_task(
|
||||
|
|
@ -841,7 +854,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_matcher_task(
|
|||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, parameters, NULL,
|
||||
NULL, NULL);
|
||||
NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_geometric_verifier_task(
|
||||
|
|
@ -851,7 +864,7 @@ Lardon3DProjectTaskCheckpointResult lardon3d_project_checkpoint_geometric_verifi
|
|||
return LARDON3D_PROJECT_TASK_CHECKPOINT_INVALID_TASK;
|
||||
}
|
||||
return checkpoint_task_internal(state, task, NULL, 0, NULL, NULL, NULL, NULL, NULL,
|
||||
parameters, NULL, NULL);
|
||||
parameters, NULL, NULL, NULL);
|
||||
}
|
||||
|
||||
static bool coherent_recovery(const Lardon3DProjectDbTask *database_task,
|
||||
|
|
|
|||
730
src/project_db.c
730
src/project_db.c
|
|
@ -12,6 +12,7 @@
|
|||
#include <time.h>
|
||||
|
||||
#include <lardon3d/project_db.h>
|
||||
#include <lardon3d/raw_development_task.h>
|
||||
#include "project_db_internal.h"
|
||||
|
||||
static const char schema_v5[] =
|
||||
|
|
@ -649,6 +650,64 @@ static const char schema_photo_quality_v21[] =
|
|||
"low_texture_fraction REAL NOT NULL,reasons TEXT NOT NULL CHECK(length(reasons)<256),"
|
||||
"PRIMARY KEY(task_id,group_id));";
|
||||
|
||||
static const char schema_selected_execution_v22[] =
|
||||
/* v22 makes the cross-request identity bridge explicit. Group IDs remain
|
||||
operational identities scoped to their respective typed Task. */
|
||||
/* Legacy v21 capture_assets rows are intentionally not backfilled: their
|
||||
source kind is unavailable and must never be guessed. */
|
||||
"CREATE TABLE IF NOT EXISTS capture_source_assets("
|
||||
"capture_id INTEGER NOT NULL,asset_id INTEGER NOT NULL,"
|
||||
"source_kind INTEGER NOT NULL CHECK(source_kind IN(1,2)),"
|
||||
"PRIMARY KEY(capture_id,asset_id),"
|
||||
"FOREIGN KEY(capture_id,asset_id) REFERENCES capture_assets(capture_id,asset_id) "
|
||||
"ON DELETE CASCADE);"
|
||||
"CREATE INDEX IF NOT EXISTS capture_source_assets_page_idx ON "
|
||||
"capture_source_assets(capture_id,asset_id);"
|
||||
"CREATE TABLE IF NOT EXISTS selected_executions("
|
||||
"execution_id INTEGER PRIMARY KEY AUTOINCREMENT CHECK(execution_id>0),"
|
||||
"quality_task_id INTEGER NOT NULL REFERENCES photo_quality_triage_tasks(task_id),"
|
||||
"campaign_task_id INTEGER NOT NULL REFERENCES acquisition_campaign_tasks(task_id),"
|
||||
"calibration_scope_id INTEGER REFERENCES sparse_calibration_scopes(scope_id),"
|
||||
"stage INTEGER NOT NULL CHECK(stage BETWEEN 1 AND 3),"
|
||||
"next_item_index INTEGER NOT NULL CHECK(next_item_index>=0 AND next_item_index<=4096),"
|
||||
"item_count INTEGER NOT NULL CHECK(item_count>0 AND item_count<=4096),"
|
||||
"created_at INTEGER NOT NULL CHECK(created_at>=0),"
|
||||
"CHECK(next_item_index<=item_count),"
|
||||
"CHECK((stage=1 AND next_item_index<item_count AND calibration_scope_id IS NULL) OR "
|
||||
"(stage=2 AND next_item_index=item_count AND calibration_scope_id IS NULL) OR "
|
||||
"(stage=3 AND next_item_index=item_count AND calibration_scope_id IS NOT NULL)),"
|
||||
"UNIQUE(quality_task_id,campaign_task_id),"
|
||||
"UNIQUE(execution_id,quality_task_id,campaign_task_id));"
|
||||
"CREATE TABLE IF NOT EXISTS selected_execution_items("
|
||||
"execution_id INTEGER NOT NULL REFERENCES selected_executions(execution_id) ON DELETE CASCADE,"
|
||||
"item_index INTEGER NOT NULL CHECK(item_index>=0 AND item_index<4096),"
|
||||
"quality_task_id INTEGER NOT NULL,campaign_task_id INTEGER NOT NULL,"
|
||||
"quality_group_id INTEGER NOT NULL CHECK(quality_group_id>0 AND quality_group_id<=4096),"
|
||||
"campaign_group_id INTEGER NOT NULL CHECK(campaign_group_id>0 AND campaign_group_id<=4096),"
|
||||
"capture_id INTEGER NOT NULL,representation_source INTEGER NOT NULL "
|
||||
"CHECK(representation_source IN(1,2)),source_asset_id INTEGER,image_id INTEGER,"
|
||||
"CHECK((representation_source=1 AND source_asset_id IS NULL) OR "
|
||||
"(representation_source=2 AND source_asset_id IS NOT NULL)),"
|
||||
"PRIMARY KEY(execution_id,item_index),UNIQUE(execution_id,quality_group_id),"
|
||||
"UNIQUE(execution_id,campaign_group_id),UNIQUE(execution_id,capture_id),"
|
||||
"FOREIGN KEY(execution_id,quality_task_id,campaign_task_id) REFERENCES "
|
||||
"selected_executions(execution_id,quality_task_id,campaign_task_id) ON DELETE CASCADE,"
|
||||
"FOREIGN KEY(quality_task_id,quality_group_id) REFERENCES "
|
||||
"photo_quality_triage_results(task_id,group_id),"
|
||||
"FOREIGN KEY(campaign_task_id,campaign_group_id) REFERENCES "
|
||||
"acquisition_campaign_captures(task_id,group_id),"
|
||||
"FOREIGN KEY(capture_id,source_asset_id) REFERENCES "
|
||||
"capture_source_assets(capture_id,asset_id),"
|
||||
"FOREIGN KEY(capture_id,image_id) REFERENCES capture_images(capture_id,image_id));"
|
||||
"CREATE TABLE IF NOT EXISTS raw_development_tasks("
|
||||
"task_id INTEGER PRIMARY KEY REFERENCES tasks(task_id) ON DELETE CASCADE,"
|
||||
"capture_id INTEGER NOT NULL,source_asset_id INTEGER NOT NULL,"
|
||||
"phase INTEGER NOT NULL CHECK(phase IN(1,2)),image_id INTEGER,"
|
||||
"CHECK((phase=1 AND image_id IS NULL) OR (phase=2 AND image_id IS NOT NULL)),"
|
||||
"FOREIGN KEY(capture_id,source_asset_id) REFERENCES "
|
||||
"capture_source_assets(capture_id,asset_id),"
|
||||
"FOREIGN KEY(capture_id,image_id) REFERENCES capture_images(capture_id,image_id));";
|
||||
|
||||
static void copy_error(char destination[LARDON3D_PROJECT_DB_ERROR_CAPACITY], const char *text) {
|
||||
if (destination) {
|
||||
(void)snprintf(destination, LARDON3D_PROJECT_DB_ERROR_CAPACITY, "%s", text ? text : "");
|
||||
|
|
@ -741,7 +800,7 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
|||
from_version != 8 && from_version != 9 && from_version != 10 && from_version != 11 &&
|
||||
from_version != 12 && from_version != 13 && from_version != 14 &&
|
||||
from_version != 15 && from_version != 16 && from_version != 17 && from_version != 18 &&
|
||||
from_version != 19 && from_version != 20) {
|
||||
from_version != 19 && from_version != 20 && from_version != 21) {
|
||||
return LARDON3D_PROJECT_DB_CORRUPT;
|
||||
}
|
||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin migration");
|
||||
|
|
@ -1163,6 +1222,22 @@ static Lardon3DProjectDbResult migrate(Lardon3DProjectDb *database, unsigned int
|
|||
"finish schema v21 migration");
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK && from_version < 22) {
|
||||
result = execute(database, schema_selected_execution_v22,
|
||||
"migrate selected execution v21 to v22");
|
||||
#ifdef LARDON3D_PROJECT_DB_TESTING
|
||||
if (result == LARDON3D_PROJECT_DB_OK &&
|
||||
getenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V22")) {
|
||||
result = execute(database, "INSERT INTO missing_v22_test_table VALUES(1)",
|
||||
"forced migration v22 failure");
|
||||
}
|
||||
#endif
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
result = execute(database,
|
||||
"UPDATE metadata SET value=22 WHERE key='schema_version' AND value=21",
|
||||
"finish schema v22 migration");
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
result = execute(database, "COMMIT", "commit migration");
|
||||
}
|
||||
|
|
@ -1297,12 +1372,16 @@ Lardon3DProjectDbResult lardon3d_project_db_open(const char *path, Lardon3DProje
|
|||
"captures",
|
||||
"capture_images",
|
||||
"capture_assets",
|
||||
"capture_source_assets",
|
||||
"raw_development_tasks",
|
||||
"capture_selections",
|
||||
"asset_derivations",
|
||||
"acquisition_campaign_tasks",
|
||||
"acquisition_campaign_captures",
|
||||
"photo_quality_triage_tasks",
|
||||
"photo_quality_triage_results"};
|
||||
"photo_quality_triage_results",
|
||||
"selected_executions",
|
||||
"selected_execution_items"};
|
||||
for (size_t index = 0; index < sizeof(required) / sizeof(required[0]) &&
|
||||
result == LARDON3D_PROJECT_DB_OK;
|
||||
++index) {
|
||||
|
|
@ -1528,6 +1607,7 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps
|
|||
const Lardon3DProjectDbSparseSfmTask *sparse_sfm,
|
||||
const Lardon3DProjectDbAcquisitionCampaignTask *campaign,
|
||||
const Lardon3DProjectDbPhotoQualityTask *photo_quality,
|
||||
const Lardon3DProjectDbRawDevelopmentTask *raw_development,
|
||||
int64_t updated_at) {
|
||||
bool typed = task_kind != NULL;
|
||||
if (!database || !valid_durable_task(snapshot, updated_at) ||
|
||||
|
|
@ -1535,6 +1615,12 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps
|
|||
(!typed && task_kind_version != 0) ||
|
||||
(source_path && !bounded_text(source_path, LARDON3D_PROJECT_DB_PATH_CAPACITY, false)) ||
|
||||
(source_path && !valid_task_id(scanset_id)) ||
|
||||
/* Raw development durability must remain exactly paired with its public
|
||||
* raw.develop durable signature, because this invariant is the only
|
||||
* crash-safe boundary for reconstructing the typed state after restart. */
|
||||
(raw_development &&
|
||||
(!task_kind || strcmp(task_kind, LARDON3D_RAW_DEVELOPMENT_TASK_KIND) != 0 ||
|
||||
task_kind_version != LARDON3D_RAW_DEVELOPMENT_TASK_KIND_VERSION)) ||
|
||||
(feature &&
|
||||
(!valid_task_id(feature->task_id) || feature->task_id != snapshot->id ||
|
||||
!valid_task_id(feature->image_id) ||
|
||||
|
|
@ -1605,6 +1691,17 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps
|
|||
photo_quality->group_count > 4096 || photo_quality->next_group_id == 0 ||
|
||||
photo_quality->next_group_id > photo_quality->group_count + 1u || !photo_quality->request ||
|
||||
photo_quality->request_size == 0 || photo_quality->request_size > INT_MAX)) ||
|
||||
(raw_development &&
|
||||
(!valid_task_id(raw_development->task_id) ||
|
||||
raw_development->task_id != snapshot->id ||
|
||||
!valid_task_id(raw_development->capture_id) ||
|
||||
!valid_task_id(raw_development->source_asset_id) ||
|
||||
(raw_development->phase != LARDON3D_RAW_DEVELOPMENT_TASK_PENDING &&
|
||||
raw_development->phase != LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED) ||
|
||||
(raw_development->phase == LARDON3D_RAW_DEVELOPMENT_TASK_PENDING &&
|
||||
(raw_development->has_image || raw_development->image_id != 0)) ||
|
||||
(raw_development->phase == LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED &&
|
||||
(!raw_development->has_image || !valid_task_id(raw_development->image_id))))) ||
|
||||
(checkpoint && !valid_checkpoint(checkpoint))) {
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
}
|
||||
|
|
@ -1714,6 +1811,37 @@ record_task_internal(Lardon3DProjectDb *database, const Lardon3DTaskDurableSnaps
|
|||
}
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK && raw_development) {
|
||||
/* Capture and SOURCE RAW are immutable Task identity. Only the publication
|
||||
phase advances, and it can advance only to an image already retained by
|
||||
that Capture. This is the typed half of the atomic restart checkpoint. */
|
||||
result = prepare(database,
|
||||
"INSERT INTO raw_development_tasks(task_id,capture_id,source_asset_id,phase,image_id) "
|
||||
"SELECT ?1,?2,?3,?4,?5 WHERE EXISTS(SELECT 1 FROM capture_source_assets "
|
||||
"WHERE capture_id=?2 AND asset_id=?3 AND source_kind=2) AND "
|
||||
"(?4=1 OR EXISTS(SELECT 1 FROM capture_images WHERE capture_id=?2 AND image_id=?5)) "
|
||||
"ON CONFLICT(task_id) DO UPDATE SET phase=excluded.phase,image_id=excluded.image_id "
|
||||
"WHERE raw_development_tasks.capture_id=excluded.capture_id AND "
|
||||
"raw_development_tasks.source_asset_id=excluded.source_asset_id AND "
|
||||
"excluded.phase>=raw_development_tasks.phase AND "
|
||||
"(raw_development_tasks.phase<2 OR raw_development_tasks.image_id=excluded.image_id)",
|
||||
&statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)raw_development->task_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)raw_development->capture_id);
|
||||
sqlite3_bind_int64(statement, 3, (sqlite3_int64)raw_development->source_asset_id);
|
||||
sqlite3_bind_int(statement, 4, (int)raw_development->phase);
|
||||
if (raw_development->has_image)
|
||||
sqlite3_bind_int64(statement, 5, (sqlite3_int64)raw_development->image_id);
|
||||
else
|
||||
sqlite3_bind_null(statement, 5);
|
||||
result = step_done(database, statement, "upsert RAW development task");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1) {
|
||||
copy_error(database->error, "Identité ou phase de développement RAW incohérente.");
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
result =
|
||||
prepare(database,
|
||||
|
|
@ -2048,7 +2176,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_task(
|
|||
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot, const char *task_kind,
|
||||
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,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task(
|
||||
|
|
@ -2060,7 +2188,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_image_import_task(
|
|||
}
|
||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint,
|
||||
source_path, scanset_id, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, updated_at);
|
||||
NULL, NULL, NULL, NULL, updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task(
|
||||
|
|
@ -2071,7 +2199,8 @@ Lardon3DProjectDbResult lardon3d_project_db_record_feature_extract_task(
|
|||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
}
|
||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
||||
parameters, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
parameters, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task(
|
||||
|
|
@ -2081,7 +2210,8 @@ Lardon3DProjectDbResult lardon3d_project_db_record_sift_extract_task(
|
|||
const Lardon3DProjectDbSiftExtractTask *parameters, int64_t updated_at) {
|
||||
if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
||||
NULL, parameters, NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
NULL, parameters, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
updated_at);
|
||||
}
|
||||
|
||||
static bool read_task(sqlite3_stmt *statement, Lardon3DProjectDbTask *task) {
|
||||
|
|
@ -2326,7 +2456,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_acquisition_campaign_task(
|
|||
never observes a valid campaign Task without its immutable request blob. */
|
||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint,
|
||||
NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters, NULL,
|
||||
updated_at);
|
||||
NULL, updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_acquisition_campaign_task(
|
||||
|
|
@ -2480,7 +2610,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_photo_quality_task(
|
|||
/* Generic runtime state and typed immutable request share the transaction;
|
||||
recovery cannot observe one without the other. */
|
||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters, NULL,
|
||||
updated_at);
|
||||
}
|
||||
|
||||
|
|
@ -2535,6 +2665,80 @@ Lardon3DProjectDbResult lardon3d_project_db_load_photo_quality_task(
|
|||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_raw_development_task(
|
||||
Lardon3DProjectDb *database, const Lardon3DTaskDurableSnapshot *snapshot,
|
||||
const char *task_kind, uint32_t task_kind_version,
|
||||
const Lardon3DProjectDbCheckpoint *checkpoint,
|
||||
const Lardon3DProjectDbRawDevelopmentTask *parameters, int64_t updated_at) {
|
||||
if (!parameters) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
return record_task_internal(database, snapshot, task_kind, task_kind_version, checkpoint, NULL, 0,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, parameters,
|
||||
updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_raw_development_task(
|
||||
Lardon3DProjectDb *database, uint64_t task_id,
|
||||
Lardon3DProjectDbRawDevelopmentTask *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 r.capture_id,r.source_asset_id,r.phase,r.image_id,cs.source_kind,ci.image_id,"
|
||||
"t.task_kind,t.task_kind_version "
|
||||
"FROM raw_development_tasks r JOIN tasks t ON t.task_id=r.task_id "
|
||||
"LEFT JOIN capture_source_assets cs ON "
|
||||
"cs.capture_id=r.capture_id AND cs.asset_id=r.source_asset_id "
|
||||
"LEFT JOIN capture_images ci ON ci.capture_id=r.capture_id AND ci.image_id=r.image_id "
|
||||
"WHERE r.task_id=?1", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)task_id);
|
||||
int code = sqlite3_step(statement);
|
||||
if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||
else if (code != SQLITE_ROW) result = sqlite_result(database, code, "load RAW development task");
|
||||
else {
|
||||
sqlite3_int64 capture = sqlite3_column_int64(statement, 0);
|
||||
sqlite3_int64 source = sqlite3_column_int64(statement, 1);
|
||||
sqlite3_int64 phase = sqlite3_column_int64(statement, 2);
|
||||
bool has_image = sqlite3_column_type(statement, 3) != SQLITE_NULL;
|
||||
sqlite3_int64 image = sqlite3_column_int64(statement, 3);
|
||||
const unsigned char *persisted_kind = sqlite3_column_text(statement, 6);
|
||||
sqlite3_int64 persisted_kind_version = sqlite3_column_int64(statement, 7);
|
||||
bool valid_types = sqlite3_column_type(statement, 0) == SQLITE_INTEGER &&
|
||||
sqlite3_column_type(statement, 1) == SQLITE_INTEGER &&
|
||||
sqlite3_column_type(statement, 2) == SQLITE_INTEGER &&
|
||||
sqlite3_column_type(statement, 6) == SQLITE_TEXT &&
|
||||
sqlite3_column_type(statement, 7) == SQLITE_INTEGER &&
|
||||
sqlite3_column_type(statement, 4) == SQLITE_INTEGER;
|
||||
bool published = phase == LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED;
|
||||
if (!valid_types || capture <= 0 || source <= 0 ||
|
||||
/* If generic task kind/version diverges from raw.develop/1,
|
||||
* treat the persisted pair as corrupt rather than attempting recovery. */
|
||||
!persisted_kind ||
|
||||
strcmp((const char *)persisted_kind, LARDON3D_RAW_DEVELOPMENT_TASK_KIND) != 0 ||
|
||||
persisted_kind_version != LARDON3D_RAW_DEVELOPMENT_TASK_KIND_VERSION ||
|
||||
sqlite3_column_int64(statement, 4) != LARDON3D_DB_CAPTURE_SOURCE_RAW ||
|
||||
(phase != LARDON3D_RAW_DEVELOPMENT_TASK_PENDING && !published) ||
|
||||
(published != has_image) || (published &&
|
||||
(image <= 0 || sqlite3_column_type(statement, 5) != SQLITE_INTEGER ||
|
||||
sqlite3_column_int64(statement, 5) != image))) {
|
||||
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||
} else {
|
||||
parameters->task_id = task_id;
|
||||
parameters->capture_id = (uint64_t)capture;
|
||||
parameters->source_asset_id = (uint64_t)source;
|
||||
parameters->phase = (Lardon3DProjectDbRawDevelopmentTaskPhase)phase;
|
||||
parameters->has_image = has_image;
|
||||
parameters->image_id = has_image ? (uint64_t)image : 0;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool valid_quality_result(const Lardon3DProjectDbPhotoQualityResult *r) {
|
||||
const Lardon3DPhotoQualityMetrics *m = r ? &r->metrics : NULL;
|
||||
bool decoded_pair = m && ((m->decoded_width == 0) == (m->decoded_height == 0));
|
||||
|
|
@ -2780,6 +2984,353 @@ Lardon3DProjectDbResult lardon3d_project_db_set_photo_quality_override(
|
|||
return result;
|
||||
}
|
||||
|
||||
static bool read_selected_execution_row(sqlite3_stmt *statement,
|
||||
Lardon3DProjectDbSelectedExecution *execution) {
|
||||
for (int column = 0; column < 8; ++column) {
|
||||
if (column != 3 && sqlite3_column_type(statement, column) != SQLITE_INTEGER)
|
||||
return false;
|
||||
}
|
||||
sqlite3_int64 id = sqlite3_column_int64(statement, 0);
|
||||
sqlite3_int64 quality = sqlite3_column_int64(statement, 1);
|
||||
sqlite3_int64 campaign = sqlite3_column_int64(statement, 2);
|
||||
bool has_scope = sqlite3_column_type(statement, 3) != SQLITE_NULL;
|
||||
sqlite3_int64 scope = has_scope ? sqlite3_column_int64(statement, 3) : 0;
|
||||
sqlite3_int64 stage = sqlite3_column_int64(statement, 4);
|
||||
sqlite3_int64 next = sqlite3_column_int64(statement, 5);
|
||||
sqlite3_int64 count = sqlite3_column_int64(statement, 6);
|
||||
sqlite3_int64 created = sqlite3_column_int64(statement, 7);
|
||||
bool relation = (stage == LARDON3D_SELECTED_EXECUTION_REPRESENTATIONS && !has_scope &&
|
||||
next < count) ||
|
||||
(stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION && !has_scope &&
|
||||
next == count) ||
|
||||
(stage == LARDON3D_SELECTED_EXECUTION_READY && has_scope && next == count);
|
||||
if (id <= 0 || quality <= 0 || campaign <= 0 || (has_scope && scope <= 0) ||
|
||||
stage < LARDON3D_SELECTED_EXECUTION_REPRESENTATIONS ||
|
||||
stage > LARDON3D_SELECTED_EXECUTION_READY || next < 0 || count <= 0 || count > 4096 ||
|
||||
next > count || created < 0 || !relation)
|
||||
return false;
|
||||
memset(execution, 0, sizeof(*execution));
|
||||
execution->execution_id = (uint64_t)id;
|
||||
execution->quality_task_id = (uint64_t)quality;
|
||||
execution->campaign_task_id = (uint64_t)campaign;
|
||||
execution->has_calibration_scope = has_scope;
|
||||
execution->calibration_scope_id = (uint64_t)scope;
|
||||
execution->stage = (Lardon3DProjectDbSelectedExecutionStage)stage;
|
||||
execution->next_item_index = (uint32_t)next;
|
||||
execution->item_count = (uint32_t)count;
|
||||
execution->created_at = created;
|
||||
return true;
|
||||
}
|
||||
|
||||
static Lardon3DProjectDbResult load_selected_execution_locked(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
Lardon3DProjectDbSelectedExecution *execution) {
|
||||
sqlite3_stmt *statement = NULL;
|
||||
Lardon3DProjectDbResult result = prepare(
|
||||
database, "SELECT execution_id,quality_task_id,campaign_task_id,calibration_scope_id,"
|
||||
"stage,next_item_index,item_count,created_at FROM selected_executions "
|
||||
"WHERE execution_id=?1", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)execution_id);
|
||||
int code = sqlite3_step(statement);
|
||||
if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||
else if (code != SQLITE_ROW) result = sqlite_result(database, code, "load selected execution");
|
||||
else if (!read_selected_execution_row(statement, execution))
|
||||
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_selected_execution(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
Lardon3DProjectDbSelectedExecution *execution) {
|
||||
if (!database || !valid_task_id(execution_id) || !execution)
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
memset(execution, 0, sizeof(*execution));
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
Lardon3DProjectDbResult result =
|
||||
load_selected_execution_locked(database, execution_id, execution);
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_selected_execution_item(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id, uint32_t item_index,
|
||||
Lardon3DProjectDbSelectedExecutionItem *item) {
|
||||
if (!database || !valid_task_id(execution_id) || !item)
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
memset(item, 0, sizeof(*item));
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
sqlite3_stmt *statement = NULL;
|
||||
Lardon3DProjectDbResult result = prepare(
|
||||
database, "SELECT i.item_index,i.quality_group_id,i.campaign_group_id,i.capture_id,"
|
||||
"i.representation_source,i.source_asset_id,i.image_id,e.item_count "
|
||||
"FROM selected_execution_items i JOIN selected_executions e "
|
||||
"ON e.execution_id=i.execution_id WHERE i.execution_id=?1 AND i.item_index=?2",
|
||||
&statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)execution_id);
|
||||
sqlite3_bind_int64(statement, 2, item_index);
|
||||
int code = sqlite3_step(statement);
|
||||
if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||
else if (code != SQLITE_ROW) result = sqlite_result(database, code, "load selected item");
|
||||
else {
|
||||
bool has_source_asset = sqlite3_column_type(statement, 5) != SQLITE_NULL;
|
||||
bool has_image = sqlite3_column_type(statement, 6) != SQLITE_NULL;
|
||||
sqlite3_int64 index = sqlite3_column_int64(statement, 0);
|
||||
sqlite3_int64 quality = sqlite3_column_int64(statement, 1);
|
||||
sqlite3_int64 campaign = sqlite3_column_int64(statement, 2);
|
||||
sqlite3_int64 capture = sqlite3_column_int64(statement, 3);
|
||||
sqlite3_int64 source_kind = sqlite3_column_int64(statement, 4);
|
||||
sqlite3_int64 source_asset = has_source_asset ? sqlite3_column_int64(statement, 5) : 0;
|
||||
sqlite3_int64 image = has_image ? sqlite3_column_int64(statement, 6) : 0;
|
||||
sqlite3_int64 count = sqlite3_column_int64(statement, 7);
|
||||
if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 1) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 2) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 3) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 4) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 7) != SQLITE_INTEGER || index < 0 || index >= count ||
|
||||
count <= 0 || count > 4096 || quality <= 0 || quality > 4096 || campaign <= 0 ||
|
||||
campaign > 4096 || capture <= 0 ||
|
||||
source_kind < LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE ||
|
||||
source_kind > LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET ||
|
||||
(source_kind == LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE && has_source_asset) ||
|
||||
(source_kind == LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET &&
|
||||
(!has_source_asset || source_asset <= 0)) || (has_image && image <= 0)) {
|
||||
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||
} else {
|
||||
item->item_index = (uint32_t)index;
|
||||
item->quality_group_id = (uint32_t)quality;
|
||||
item->campaign_group_id = (uint32_t)campaign;
|
||||
item->capture_id = (uint64_t)capture;
|
||||
item->representation_source = (Lardon3DProjectDbSelectedRepresentationSource)source_kind;
|
||||
item->source_asset_id = (uint64_t)source_asset;
|
||||
item->has_image = has_image;
|
||||
item->image_id = (uint64_t)image;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static Lardon3DProjectDbResult compare_selected_items_locked(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const Lardon3DProjectDbSelectedExecutionItem *items, size_t item_count) {
|
||||
sqlite3_stmt *statement = NULL;
|
||||
Lardon3DProjectDbResult result = prepare(
|
||||
database, "SELECT item_index,quality_group_id,campaign_group_id,capture_id,"
|
||||
"representation_source,source_asset_id,image_id "
|
||||
"FROM selected_execution_items WHERE execution_id=?1 ORDER BY item_index",
|
||||
&statement);
|
||||
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)execution_id);
|
||||
size_t index = 0;
|
||||
int code;
|
||||
while ((code = sqlite3_step(statement)) == SQLITE_ROW && index < item_count) {
|
||||
if (sqlite3_column_int64(statement, 0) != (sqlite3_int64)index ||
|
||||
sqlite3_column_int64(statement, 1) != items[index].quality_group_id ||
|
||||
sqlite3_column_int64(statement, 2) != items[index].campaign_group_id ||
|
||||
sqlite3_column_int64(statement, 3) != (sqlite3_int64)items[index].capture_id ||
|
||||
sqlite3_column_int64(statement, 4) != items[index].representation_source ||
|
||||
((items[index].representation_source == LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE &&
|
||||
sqlite3_column_type(statement, 5) != SQLITE_NULL) ||
|
||||
(items[index].representation_source == LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET &&
|
||||
(sqlite3_column_type(statement, 5) != SQLITE_INTEGER ||
|
||||
sqlite3_column_int64(statement, 5) != (sqlite3_int64)items[index].source_asset_id)))) {
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
break;
|
||||
}
|
||||
++index;
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK && (code != SQLITE_DONE || index != item_count))
|
||||
result = code == SQLITE_ROW || code == SQLITE_DONE
|
||||
? LARDON3D_PROJECT_DB_CONSTRAINT
|
||||
: sqlite_result(database, code, "compare selected items");
|
||||
sqlite3_finalize(statement);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_create_selected_execution(
|
||||
Lardon3DProjectDb *database, uint64_t quality_task_id,
|
||||
uint64_t campaign_task_id, const Lardon3DProjectDbSelectedExecutionItem *items,
|
||||
size_t item_count, int64_t created_at,
|
||||
Lardon3DProjectDbSelectedExecution *execution) {
|
||||
if (!database || !valid_task_id(quality_task_id) || !valid_task_id(campaign_task_id) ||
|
||||
!items || item_count == 0 || item_count > 4096 || created_at < 0 || !execution)
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
for (size_t index = 0; index < item_count; ++index) {
|
||||
if (items[index].item_index != index || items[index].quality_group_id == 0 ||
|
||||
items[index].campaign_group_id == 0 || !valid_task_id(items[index].capture_id) ||
|
||||
(items[index].representation_source != LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE &&
|
||||
items[index].representation_source != LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET) ||
|
||||
(items[index].representation_source == LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE &&
|
||||
items[index].source_asset_id != 0) ||
|
||||
(items[index].representation_source == LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET &&
|
||||
!valid_task_id(items[index].source_asset_id)) ||
|
||||
items[index].has_image || items[index].image_id != 0)
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
}
|
||||
memset(execution, 0, sizeof(*execution));
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin selected snapshot");
|
||||
sqlite3_stmt *statement = NULL;
|
||||
uint64_t existing_id = 0;
|
||||
bool created_new = false;
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
result = prepare(database, "SELECT execution_id,item_count,created_at FROM selected_executions "
|
||||
"WHERE quality_task_id=?1 AND campaign_task_id=?2", &statement);
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)quality_task_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)campaign_task_id);
|
||||
int code = sqlite3_step(statement);
|
||||
if (code == SQLITE_ROW) {
|
||||
existing_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
||||
if (sqlite3_column_int64(statement, 1) != (sqlite3_int64)item_count ||
|
||||
sqlite3_column_int64(statement, 2) != created_at)
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
} else if (code != SQLITE_DONE) {
|
||||
result = sqlite_result(database, code, "find selected snapshot");
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
statement = NULL;
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK && existing_id != 0)
|
||||
result = compare_selected_items_locked(database, existing_id, items, item_count);
|
||||
if (result == LARDON3D_PROJECT_DB_OK && existing_id == 0) {
|
||||
result = prepare(database,
|
||||
"INSERT INTO selected_executions(quality_task_id,campaign_task_id,stage,next_item_index,"
|
||||
"item_count,created_at) SELECT ?1,?2,1,0,?3,?4 WHERE EXISTS(SELECT 1 FROM "
|
||||
"photo_quality_triage_tasks q JOIN acquisition_campaign_tasks c ON q.scanset_id=c.scanset_id "
|
||||
"WHERE q.task_id=?1 AND c.task_id=?2 AND q.next_group_id=q.group_count+1)", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)quality_task_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)campaign_task_id);
|
||||
sqlite3_bind_int64(statement, 3, (sqlite3_int64)item_count);
|
||||
sqlite3_bind_int64(statement, 4, created_at);
|
||||
result = step_done(database, statement, "create selected snapshot");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1)
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_int64 id = sqlite3_last_insert_rowid(database->connection);
|
||||
if (id <= 0) result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||
else {
|
||||
existing_id = (uint64_t)id;
|
||||
created_new = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (size_t index = 0; result == LARDON3D_PROJECT_DB_OK && created_new &&
|
||||
index < item_count; ++index) {
|
||||
result = prepare(database,
|
||||
"INSERT INTO selected_execution_items(execution_id,item_index,quality_task_id,"
|
||||
"campaign_task_id,quality_group_id,campaign_group_id,capture_id,"
|
||||
"representation_source,source_asset_id) "
|
||||
"SELECT ?1,?2,?3,?4,?5,?6,?7,?8,?9 WHERE EXISTS(SELECT 1 FROM "
|
||||
"photo_quality_triage_results q JOIN acquisition_campaign_captures c "
|
||||
"ON c.task_id=?4 AND c.group_id=?6 AND c.capture_id=?7 "
|
||||
"WHERE q.task_id=?3 AND q.group_id=?5 AND "
|
||||
"(q.human_override=1 OR (q.human_override=0 AND q.recommendation=1))) "
|
||||
"AND (?8=1 OR EXISTS(SELECT 1 FROM capture_source_assets ca WHERE ca.capture_id=?7 "
|
||||
"AND ca.asset_id=?9 AND ca.source_kind=2))", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)existing_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)index);
|
||||
sqlite3_bind_int64(statement, 3, (sqlite3_int64)quality_task_id);
|
||||
sqlite3_bind_int64(statement, 4, (sqlite3_int64)campaign_task_id);
|
||||
sqlite3_bind_int64(statement, 5, items[index].quality_group_id);
|
||||
sqlite3_bind_int64(statement, 6, items[index].campaign_group_id);
|
||||
sqlite3_bind_int64(statement, 7, (sqlite3_int64)items[index].capture_id);
|
||||
sqlite3_bind_int(statement, 8, (int)items[index].representation_source);
|
||||
if (items[index].representation_source == LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET)
|
||||
sqlite3_bind_int64(statement, 9, (sqlite3_int64)items[index].source_asset_id);
|
||||
else
|
||||
sqlite3_bind_null(statement, 9);
|
||||
result = step_done(database, statement, "insert selected item");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1)
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = execute(database, "COMMIT", "commit selected snapshot");
|
||||
else
|
||||
(void)execute(database, "ROLLBACK", "rollback selected snapshot");
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = load_selected_execution_locked(database, existing_id, execution);
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_selected_representation(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id, uint32_t item_index,
|
||||
uint64_t image_id, uint32_t next_item_index) {
|
||||
if (!database || !valid_task_id(execution_id) || !valid_task_id(image_id) ||
|
||||
item_index == UINT32_MAX || next_item_index != item_index + 1u)
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin representation");
|
||||
sqlite3_stmt *statement = NULL;
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = prepare(database,
|
||||
"UPDATE selected_execution_items SET image_id=?3 WHERE execution_id=?1 AND item_index=?2 "
|
||||
"AND (image_id IS NULL OR image_id=?3) AND EXISTS(SELECT 1 FROM capture_images ci "
|
||||
"WHERE ci.capture_id=selected_execution_items.capture_id AND ci.image_id=?3)", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)execution_id);
|
||||
sqlite3_bind_int64(statement, 2, item_index);
|
||||
sqlite3_bind_int64(statement, 3, (sqlite3_int64)image_id);
|
||||
result = step_done(database, statement, "retain selected representation");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1)
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = prepare(database,
|
||||
"UPDATE selected_executions SET next_item_index=?3,stage=CASE WHEN ?3=item_count THEN 2 "
|
||||
"ELSE 1 END WHERE execution_id=?1 AND stage IN(1,2) AND "
|
||||
"(next_item_index=?2 OR next_item_index=?3) AND item_count>=?3", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)execution_id);
|
||||
sqlite3_bind_int64(statement, 2, item_index);
|
||||
sqlite3_bind_int64(statement, 3, next_item_index);
|
||||
result = step_done(database, statement, "advance representation cursor");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1)
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK) result = execute(database, "COMMIT", "commit representation");
|
||||
else (void)execute(database, "ROLLBACK", "rollback representation");
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_assign_selected_calibration_scope(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
uint64_t calibration_scope_id) {
|
||||
if (!database || !valid_task_id(execution_id) || !valid_task_id(calibration_scope_id))
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
sqlite3_stmt *statement = NULL;
|
||||
Lardon3DProjectDbResult result = prepare(database,
|
||||
"UPDATE selected_executions SET calibration_scope_id=?2,stage=3 WHERE execution_id=?1 "
|
||||
"AND next_item_index=item_count AND (stage=2 OR (stage=3 AND calibration_scope_id=?2)) "
|
||||
"AND NOT EXISTS(SELECT 1 FROM selected_execution_items i WHERE i.execution_id=?1 AND "
|
||||
"(i.image_id IS NULL OR NOT EXISTS(SELECT 1 FROM sparse_calibration_scope_images s "
|
||||
"WHERE s.scope_id=?2 AND s.image_id=i.image_id)))", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)execution_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)calibration_scope_id);
|
||||
result = step_done(database, statement, "assign selected calibration scope");
|
||||
if (result == LARDON3D_PROJECT_DB_OK && sqlite3_changes(database->connection) != 1)
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool valid_catalog_id(uint64_t value) { return value > 0 && value <= INT64_MAX; }
|
||||
|
||||
static bool valid_original_name(const char *name) {
|
||||
|
|
@ -3305,6 +3856,50 @@ Lardon3DProjectDbResult lardon3d_project_db_register_image_asset(
|
|||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_image_asset(
|
||||
Lardon3DProjectDb *database, uint64_t asset_id, Lardon3DProjectDbImageAsset *asset) {
|
||||
if (!database || !valid_catalog_id(asset_id) || !asset)
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
memset(asset, 0, sizeof(*asset));
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
sqlite3_stmt *statement = NULL;
|
||||
Lardon3DProjectDbResult result = prepare(database,
|
||||
"SELECT asset_id,sha256,path,size_bytes,state,created_at FROM image_assets "
|
||||
"WHERE asset_id=?1", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)asset_id);
|
||||
int code = sqlite3_step(statement);
|
||||
if (code == SQLITE_DONE) result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
||||
else if (code != SQLITE_ROW) result = sqlite_result(database, code, "load image asset");
|
||||
else {
|
||||
sqlite3_int64 stored_id = sqlite3_column_int64(statement, 0);
|
||||
sqlite3_int64 stored_size = sqlite3_column_int64(statement, 3);
|
||||
sqlite3_int64 stored_state = sqlite3_column_int64(statement, 4);
|
||||
sqlite3_int64 stored_created = sqlite3_column_int64(statement, 5);
|
||||
const void *stored_hash = sqlite3_column_blob(statement, 1);
|
||||
if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER || stored_id <= 0 ||
|
||||
sqlite3_column_type(statement, 1) != SQLITE_BLOB || !stored_hash ||
|
||||
sqlite3_column_bytes(statement, 1) != LARDON3D_PROJECT_DB_SHA256_SIZE ||
|
||||
!copy_column(statement, 2, asset->path, sizeof(asset->path)) ||
|
||||
!valid_relative_asset_path(asset->path) || stored_size < 0 ||
|
||||
stored_state != LARDON3D_DB_IMAGE_ASSET_READY || stored_created < 0) {
|
||||
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||
} else {
|
||||
asset->asset_id = (uint64_t)stored_id;
|
||||
memcpy(asset->sha256, stored_hash, sizeof(asset->sha256));
|
||||
asset->size_bytes = (uint64_t)stored_size;
|
||||
asset->state = LARDON3D_DB_IMAGE_ASSET_READY;
|
||||
asset->created_at = stored_created;
|
||||
if (!canonical_asset_path(asset->sha256, asset->path))
|
||||
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_register_image(
|
||||
Lardon3DProjectDb *database, uint64_t scanset_id,
|
||||
const unsigned char sha256[LARDON3D_PROJECT_DB_SHA256_SIZE], const char *asset_path,
|
||||
|
|
@ -3785,6 +4380,10 @@ static bool valid_capture_asset_role(Lardon3DProjectDbCaptureAssetRole role) {
|
|||
return role == LARDON3D_DB_CAPTURE_ASSET_SOURCE || role == LARDON3D_DB_CAPTURE_ASSET_DERIVED;
|
||||
}
|
||||
|
||||
static bool valid_capture_source_kind(Lardon3DProjectDbCaptureSourceKind kind) {
|
||||
return kind == LARDON3D_DB_CAPTURE_SOURCE_JPEG || kind == LARDON3D_DB_CAPTURE_SOURCE_RAW;
|
||||
}
|
||||
|
||||
static bool valid_asset_derivation_kind(Lardon3DProjectDbAssetDerivationKind kind) {
|
||||
return kind == LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED;
|
||||
}
|
||||
|
|
@ -3936,6 +4535,109 @@ Lardon3DProjectDbResult lardon3d_project_db_attach_capture_source_asset(
|
|||
return LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_record_capture_source_asset(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t asset_id,
|
||||
Lardon3DProjectDbCaptureSourceKind source_kind) {
|
||||
if (!database || !valid_catalog_id(capture_id) || !valid_catalog_id(asset_id) ||
|
||||
!valid_capture_source_kind(source_kind))
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
Lardon3DProjectDbResult result = execute(database, "BEGIN IMMEDIATE", "begin source provenance");
|
||||
sqlite3_stmt *statement = NULL;
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = prepare(database,
|
||||
"INSERT INTO capture_assets(capture_id,asset_id,role) VALUES(?1,?2,1) "
|
||||
"ON CONFLICT(capture_id,asset_id) DO NOTHING", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)asset_id);
|
||||
result = step_done(database, statement, "attach explicit source asset");
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = prepare(database,
|
||||
"INSERT INTO capture_source_assets(capture_id,asset_id,source_kind) "
|
||||
"SELECT ?1,?2,?3 WHERE EXISTS(SELECT 1 FROM capture_assets "
|
||||
"WHERE capture_id=?1 AND asset_id=?2 AND role=1) "
|
||||
"ON CONFLICT(capture_id,asset_id) DO NOTHING", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)asset_id);
|
||||
sqlite3_bind_int(statement, 3, (int)source_kind);
|
||||
result = step_done(database, statement, "retain explicit source kind");
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = prepare(database,
|
||||
"SELECT ca.role,cs.source_kind FROM capture_assets ca LEFT JOIN capture_source_assets cs "
|
||||
"ON cs.capture_id=ca.capture_id AND cs.asset_id=ca.asset_id "
|
||||
"WHERE ca.capture_id=?1 AND ca.asset_id=?2", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)asset_id);
|
||||
int code = sqlite3_step(statement);
|
||||
if (code != SQLITE_ROW) {
|
||||
result = code == SQLITE_DONE ? LARDON3D_PROJECT_DB_CONSTRAINT
|
||||
: sqlite_result(database, code, "verify source provenance");
|
||||
} else if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 1) != SQLITE_INTEGER ||
|
||||
sqlite3_column_int(statement, 0) != LARDON3D_DB_CAPTURE_ASSET_SOURCE ||
|
||||
sqlite3_column_int(statement, 1) != (int)source_kind) {
|
||||
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
statement = NULL;
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK)
|
||||
result = execute(database, "COMMIT", "commit source provenance");
|
||||
else
|
||||
(void)execute(database, "ROLLBACK", "rollback source provenance");
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_list_capture_source_assets(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t after_asset_id,
|
||||
Lardon3DProjectDbCaptureSourceAsset *assets, size_t capacity, size_t *count) {
|
||||
if (count) *count = 0;
|
||||
if (!database || !valid_catalog_id(capture_id) || after_asset_id > INT64_MAX || !assets ||
|
||||
!count || capacity == 0 || capacity > LARDON3D_PROJECT_DB_CATALOG_PAGE_MAX)
|
||||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
|
||||
(void)pthread_mutex_lock(&database->mutex);
|
||||
sqlite3_stmt *statement = NULL;
|
||||
Lardon3DProjectDbResult result = prepare(database,
|
||||
"SELECT capture_id,asset_id,source_kind FROM capture_source_assets WHERE capture_id=?1 "
|
||||
"AND asset_id>?2 ORDER BY asset_id LIMIT ?3", &statement);
|
||||
if (result == LARDON3D_PROJECT_DB_OK) {
|
||||
sqlite3_bind_int64(statement, 1, (sqlite3_int64)capture_id);
|
||||
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_asset_id);
|
||||
sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
||||
int code = SQLITE_DONE;
|
||||
while (*count < capacity && (code = sqlite3_step(statement)) == SQLITE_ROW) {
|
||||
sqlite3_int64 stored_capture = sqlite3_column_int64(statement, 0);
|
||||
sqlite3_int64 stored_asset = sqlite3_column_int64(statement, 1);
|
||||
sqlite3_int64 stored_kind = sqlite3_column_int64(statement, 2);
|
||||
if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 1) != SQLITE_INTEGER ||
|
||||
sqlite3_column_type(statement, 2) != SQLITE_INTEGER || stored_capture <= 0 ||
|
||||
stored_asset <= 0 ||
|
||||
!valid_capture_source_kind((Lardon3DProjectDbCaptureSourceKind)stored_kind)) {
|
||||
result = LARDON3D_PROJECT_DB_CORRUPT;
|
||||
break;
|
||||
}
|
||||
assets[*count] = (Lardon3DProjectDbCaptureSourceAsset){
|
||||
.capture_id = (uint64_t)stored_capture, .asset_id = (uint64_t)stored_asset,
|
||||
.source_kind = (Lardon3DProjectDbCaptureSourceKind)stored_kind};
|
||||
++*count;
|
||||
}
|
||||
if (result == LARDON3D_PROJECT_DB_OK && *count < capacity && code != SQLITE_DONE)
|
||||
result = sqlite_result(database, code, "list capture source provenance");
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
(void)pthread_mutex_unlock(&database->mutex);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_list_capture_assets(
|
||||
Lardon3DProjectDb *database, uint64_t capture_id, uint64_t after_asset_id,
|
||||
Lardon3DProjectDbCaptureAsset *assets, size_t capacity, size_t *count) {
|
||||
|
|
@ -5365,7 +6067,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_visual_index_update_task(
|
|||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
}
|
||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||
NULL, parameters, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
NULL, parameters, NULL, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_visual_index_update_task(
|
||||
|
|
@ -5412,7 +6114,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_candidate_pair_generate_task(
|
|||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
}
|
||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||
NULL, NULL, parameters, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
NULL, NULL, parameters, NULL, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_candidate_pair_generate_task(
|
||||
|
|
@ -5468,7 +6170,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_matcher_task(
|
|||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
}
|
||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||
NULL, NULL, NULL, parameters, NULL, NULL, NULL, NULL, updated_at);
|
||||
NULL, NULL, NULL, parameters, NULL, NULL, NULL, NULL, NULL, updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_matcher_task(
|
||||
|
|
@ -5526,7 +6228,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_geometric_verifier_task(
|
|||
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
||||
}
|
||||
return record_task_internal(db, snapshot, kind, version, checkpoint, NULL, 0, NULL,
|
||||
NULL, NULL, NULL, NULL, parameters, NULL, NULL, NULL, updated_at);
|
||||
NULL, NULL, NULL, NULL, parameters, NULL, NULL, NULL, NULL, updated_at);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbResult lardon3d_project_db_load_geometric_verifier_task(
|
||||
|
|
@ -5599,7 +6301,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_track_builder_task(
|
|||
}
|
||||
Lardon3DProjectDbResult result = record_task_internal(
|
||||
db, snapshot, kind, version, checkpoint, NULL, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, updated_at);
|
||||
NULL, NULL, NULL, updated_at);
|
||||
if (result != LARDON3D_PROJECT_DB_OK) return result;
|
||||
(void)pthread_mutex_lock(&db->mutex);
|
||||
result = execute(db, "BEGIN IMMEDIATE", "begin track builder task");
|
||||
|
|
@ -5710,7 +6412,7 @@ Lardon3DProjectDbResult lardon3d_project_db_record_sparse_sfm_task(
|
|||
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, NULL, NULL, updated_at);
|
||||
NULL, NULL, NULL, NULL, parameters, NULL, NULL, NULL, updated_at);
|
||||
}
|
||||
|
||||
static bool read_u64_blob(sqlite3_stmt *statement, int column, uint64_t *value) {
|
||||
|
|
|
|||
|
|
@ -336,3 +336,47 @@ extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_develop_to_capture(
|
|||
} catch (const std::bad_alloc &) { return LARDON3D_RAW_DEVELOPMENT_OUT_OF_MEMORY; }
|
||||
catch (...) { return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR; }
|
||||
}
|
||||
|
||||
extern "C" Lardon3DRawDevelopmentResult lardon3d_raw_develop_asset_to_capture(
|
||||
Lardon3DAppState *state, uint64_t capture_id, uint64_t source_asset_id,
|
||||
uint64_t producer_task_id, int64_t created_at, Lardon3DRawDevelopmentOutput *output) {
|
||||
if (!state || !state->project_loaded || !state->project_db || capture_id == 0 ||
|
||||
source_asset_id == 0 || created_at < 0 || !output)
|
||||
return LARDON3D_RAW_DEVELOPMENT_INVALID_ARGUMENT;
|
||||
std::memset(output, 0, sizeof(*output));
|
||||
try {
|
||||
bool explicit_raw = false;
|
||||
uint64_t after_asset_id = source_asset_id - 1u;
|
||||
Lardon3DProjectDbCaptureSourceAsset relation{};
|
||||
size_t count = 0;
|
||||
Lardon3DProjectDbResult listed = lardon3d_project_db_list_capture_source_assets(
|
||||
state->project_db, capture_id, after_asset_id, &relation, 1, &count);
|
||||
if (listed != LARDON3D_PROJECT_DB_OK) return db_result(listed);
|
||||
explicit_raw = count == 1 && relation.asset_id == source_asset_id &&
|
||||
relation.source_kind == LARDON3D_DB_CAPTURE_SOURCE_RAW;
|
||||
if (!explicit_raw) return LARDON3D_RAW_DEVELOPMENT_CONSTRAINT;
|
||||
|
||||
Lardon3DProjectDbImageAsset asset{};
|
||||
Lardon3DProjectDbResult loaded = lardon3d_project_db_load_image_asset(
|
||||
state->project_db, source_asset_id, &asset);
|
||||
if (loaded != LARDON3D_PROJECT_DB_OK) return db_result(loaded);
|
||||
char managed_source[LARDON3D_APP_STATE_PATH_CAPACITY];
|
||||
if (!asset_file_path(state, asset, managed_source)) return LARDON3D_RAW_DEVELOPMENT_IO_ERROR;
|
||||
if (!verify_managed_asset(managed_source, asset.sha256))
|
||||
return LARDON3D_RAW_DEVELOPMENT_SOURCE_CHANGED;
|
||||
|
||||
// The pathname passed to the frozen developer is resolved only after the
|
||||
// explicit durable ID and RAW relation are validated. Republishing these
|
||||
// immutable managed bytes must converge to that same asset ID.
|
||||
Lardon3DRawDevelopmentResult result = lardon3d_raw_develop_to_capture(
|
||||
state, capture_id, managed_source, producer_task_id, created_at, output);
|
||||
if (result != LARDON3D_RAW_DEVELOPMENT_OK) return result;
|
||||
return output->source_asset.asset_id == source_asset_id
|
||||
? LARDON3D_RAW_DEVELOPMENT_OK
|
||||
: LARDON3D_RAW_DEVELOPMENT_CONSTRAINT;
|
||||
} catch (const std::bad_alloc &) {
|
||||
return LARDON3D_RAW_DEVELOPMENT_OUT_OF_MEMORY;
|
||||
} catch (...) {
|
||||
return LARDON3D_RAW_DEVELOPMENT_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
197
src/raw_development_task.cpp
Normal file
197
src/raw_development_task.cpp
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
extern "C" {
|
||||
#include <lardon3d/project.h>
|
||||
#include <lardon3d/raw_development.h>
|
||||
#include <lardon3d/raw_development_task.h>
|
||||
#include <lardon3d/task_queue.h>
|
||||
}
|
||||
|
||||
#include <cstdio>
|
||||
#include <new>
|
||||
|
||||
namespace {
|
||||
/* One admitted RAW execution owns a conservative allowance for the bounded
|
||||
* 40 MP decoder image, LibRaw storage, BGR/PNG buffers, validation decode, and
|
||||
* immutable-asset publication. This is an operational hardware bound, not a
|
||||
* scientific limit on the number of Captures. The Queue/Governor reservation
|
||||
* exists only for this one Task callback and is released by Task runtime on
|
||||
* every terminal path. */
|
||||
constexpr uint64_t kRawWorkingBytes = UINT64_C(2) * 1024u * 1024u * 1024u;
|
||||
|
||||
struct Context {
|
||||
char project_path[LARDON3D_APP_STATE_PATH_CAPACITY]{};
|
||||
Lardon3DProjectDb *database{};
|
||||
Lardon3DResourceGovernor *governor{};
|
||||
uint64_t capture_id{};
|
||||
uint64_t source_asset_id{};
|
||||
};
|
||||
|
||||
void destroy(void *value) { delete static_cast<Context *>(value); }
|
||||
|
||||
void runtime(const Context *context, Lardon3DAppState *state) {
|
||||
lardon3d_app_state_init(state);
|
||||
state->project_loaded = true;
|
||||
state->project_db = context->database;
|
||||
state->resource_governor = context->governor;
|
||||
std::snprintf(state->project_path, sizeof(state->project_path), "%s", context->project_path);
|
||||
}
|
||||
|
||||
bool checkpoint(Context *context, Lardon3DTask *task,
|
||||
Lardon3DProjectDbRawDevelopmentTaskPhase phase, uint64_t image_id) {
|
||||
Lardon3DAppState state;
|
||||
runtime(context, &state);
|
||||
Lardon3DProjectDbRawDevelopmentTask parameters{
|
||||
lardon3d_task_id(task), context->capture_id, context->source_asset_id, phase,
|
||||
image_id != 0, image_id};
|
||||
return lardon3d_project_checkpoint_raw_development_task(&state, task, ¶meters) ==
|
||||
LARDON3D_PROJECT_TASK_CHECKPOINT_OK;
|
||||
}
|
||||
|
||||
bool run_impl(Lardon3DTask *task, Context *context) {
|
||||
Lardon3DProjectDbRawDevelopmentTask persisted{};
|
||||
if (lardon3d_project_db_load_raw_development_task(
|
||||
context->database, lardon3d_task_id(task), &persisted) != LARDON3D_PROJECT_DB_OK)
|
||||
return lardon3d_task_fail(task, "État durable du développement RAW invalide.");
|
||||
if (!lardon3d_task_checkpoint(task)) return false;
|
||||
if (persisted.phase == LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED) {
|
||||
return lardon3d_task_set_progress(task, 100, "Développement RAW déjà publié.") &&
|
||||
checkpoint(context, task, LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED,
|
||||
persisted.image_id);
|
||||
}
|
||||
|
||||
Lardon3DProjectDbCapture capture{};
|
||||
if (lardon3d_project_db_load_capture(context->database, context->capture_id, &capture) !=
|
||||
LARDON3D_PROJECT_DB_OK)
|
||||
return lardon3d_task_fail(task, "Capture du développement RAW introuvable.");
|
||||
Lardon3DAppState state;
|
||||
runtime(context, &state);
|
||||
Lardon3DRawDevelopmentOutput output{};
|
||||
Lardon3DRawDevelopmentResult developed = lardon3d_raw_develop_asset_to_capture(
|
||||
&state, context->capture_id, context->source_asset_id, lardon3d_task_id(task),
|
||||
capture.created_at, &output);
|
||||
if (developed != LARDON3D_RAW_DEVELOPMENT_OK)
|
||||
return lardon3d_task_fail(task, "Développement RAW S3-B1 impossible.");
|
||||
|
||||
/* Immutable output publication may precede this checkpoint if the process
|
||||
* dies. The frozen developer is content-addressed and exact retry converges;
|
||||
* once this atomic typed/generic checkpoint succeeds, recovery never guesses
|
||||
* which image was published. */
|
||||
if (!lardon3d_task_set_progress(task, 100, "Représentation RAW publiée.") ||
|
||||
!checkpoint(context, task, LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED,
|
||||
output.image.image_id))
|
||||
return lardon3d_task_fail(task, "Checkpoint du développement RAW impossible.");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool run(Lardon3DTask *task, void *value) noexcept {
|
||||
try {
|
||||
return run_impl(task, static_cast<Context *>(value));
|
||||
} catch (const std::bad_alloc &) {
|
||||
return lardon3d_task_fail(task, "Mémoire insuffisante pour le développement RAW.");
|
||||
} catch (...) {
|
||||
return lardon3d_task_fail(task, "Erreur interne du développement RAW.");
|
||||
}
|
||||
}
|
||||
|
||||
void finished(const Lardon3DTask *task, void *value) noexcept {
|
||||
try {
|
||||
Lardon3DAppState state;
|
||||
runtime(static_cast<Context *>(value), &state);
|
||||
(void)lardon3d_project_checkpoint_task(&state, task);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
Context *make_context(const Lardon3DTaskReconstructionContext *runtime_context,
|
||||
uint64_t capture_id, uint64_t source_asset_id) {
|
||||
if (!runtime_context || !runtime_context->project_path ||
|
||||
!runtime_context->project_path[0] || !runtime_context->project_db ||
|
||||
!runtime_context->resource_governor)
|
||||
return nullptr;
|
||||
Context *context = new (std::nothrow) Context;
|
||||
if (!context) return nullptr;
|
||||
int written = std::snprintf(context->project_path, sizeof(context->project_path), "%s",
|
||||
runtime_context->project_path);
|
||||
if (written <= 0 || written >= static_cast<int>(sizeof(context->project_path))) {
|
||||
delete context;
|
||||
return nullptr;
|
||||
}
|
||||
context->database = runtime_context->project_db;
|
||||
context->governor = runtime_context->resource_governor;
|
||||
context->capture_id = capture_id;
|
||||
context->source_asset_id = source_asset_id;
|
||||
return context;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" bool lardon3d_raw_development_task_reconstruct(
|
||||
const Lardon3DTaskDurableSnapshot *snapshot, void *value,
|
||||
Lardon3DTaskKindBinding *binding) {
|
||||
try {
|
||||
auto *runtime_context = static_cast<Lardon3DTaskReconstructionContext *>(value);
|
||||
if (!snapshot || !runtime_context || !binding) return false;
|
||||
Lardon3DProjectDbRawDevelopmentTask persisted{};
|
||||
if (lardon3d_project_db_load_raw_development_task(
|
||||
runtime_context->project_db, snapshot->id, &persisted) != LARDON3D_PROJECT_DB_OK)
|
||||
return false;
|
||||
Context *context = make_context(runtime_context, persisted.capture_id,
|
||||
persisted.source_asset_id);
|
||||
if (!context) return false;
|
||||
*binding = {run, context, destroy, finished, context};
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" Lardon3DTask *lardon3d_project_create_raw_development_task(
|
||||
Lardon3DAppState *state, uint64_t capture_id, uint64_t source_asset_id,
|
||||
uint64_t *task_id) {
|
||||
if (task_id) *task_id = 0;
|
||||
try {
|
||||
if (!state || !state->project_loaded || !state->project_db ||
|
||||
!state->resource_governor || !task_id || capture_id == 0 || source_asset_id == 0)
|
||||
return nullptr;
|
||||
Lardon3DTaskReconstructionContext runtime_context{
|
||||
state->project_path, state->project_db, state->resource_governor, nullptr};
|
||||
Context *context = make_context(&runtime_context, capture_id, source_asset_id);
|
||||
if (!context) return nullptr;
|
||||
uint64_t id = 0;
|
||||
if (lardon3d_project_db_allocate_task_id(state->project_db, &id) != LARDON3D_PROJECT_DB_OK) {
|
||||
delete context;
|
||||
return nullptr;
|
||||
}
|
||||
Lardon3DResourceEstimate estimate{kRawWorkingBytes + sizeof(Context), 0, 0, 0,
|
||||
1, 1, 1, 0, 1, LARDON3D_RESOURCE_TASK_MIXED};
|
||||
Lardon3DTask *task = lardon3d_task_create_typed(
|
||||
"Développement RAW S3-B1", &estimate, LARDON3D_RAW_DEVELOPMENT_TASK_KIND,
|
||||
LARDON3D_RAW_DEVELOPMENT_TASK_KIND_VERSION, run, context, destroy);
|
||||
if (!task || !lardon3d_task_assign_id(task, id) ||
|
||||
!lardon3d_task_set_finished_callback(task, finished, context) ||
|
||||
!checkpoint(context, task, LARDON3D_RAW_DEVELOPMENT_TASK_PENDING, 0)) {
|
||||
lardon3d_task_destroy(task);
|
||||
return nullptr;
|
||||
}
|
||||
*task_id = id;
|
||||
return task;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" bool lardon3d_project_enqueue_raw_development(
|
||||
Lardon3DAppState *state, uint64_t capture_id, uint64_t source_asset_id,
|
||||
uint64_t *task_id) {
|
||||
try {
|
||||
if (!state || !state->task_queue) return false;
|
||||
Lardon3DTask *task = lardon3d_project_create_raw_development_task(
|
||||
state, capture_id, source_asset_id, task_id);
|
||||
if (!task) return false;
|
||||
if (!lardon3d_task_queue_add(state->task_queue, task, nullptr)) {
|
||||
lardon3d_task_destroy(task);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,9 @@
|
|||
#ifdef LARDON3D_PHOTO_QUALITY_TASK_AVAILABLE
|
||||
#include <lardon3d/photo_quality_task.h>
|
||||
#endif
|
||||
#ifdef LARDON3D_RAW_DEVELOPMENT_TASK_AVAILABLE
|
||||
#include <lardon3d/raw_development_task.h>
|
||||
#endif
|
||||
#include <lardon3d/geometric_verifier_task.h>
|
||||
#include <lardon3d/import_task.h>
|
||||
#include <lardon3d/matcher_task.h>
|
||||
|
|
@ -24,6 +27,13 @@
|
|||
|
||||
const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||
static const Lardon3DTaskKindDescriptor descriptors[] = {
|
||||
#ifdef LARDON3D_RAW_DEVELOPMENT_TASK_AVAILABLE
|
||||
{
|
||||
.kind = LARDON3D_RAW_DEVELOPMENT_TASK_KIND,
|
||||
.kind_version = LARDON3D_RAW_DEVELOPMENT_TASK_KIND_VERSION,
|
||||
.reconstruct = lardon3d_raw_development_task_reconstruct,
|
||||
},
|
||||
#endif
|
||||
#ifdef LARDON3D_PHOTO_QUALITY_TASK_AVAILABLE
|
||||
{
|
||||
.kind = LARDON3D_PHOTO_QUALITY_TASK_KIND,
|
||||
|
|
|
|||
|
|
@ -146,6 +146,24 @@ bool capture_assets(Lardon3DProjectDb *database, uint64_t capture_id, size_t exp
|
|||
return true;
|
||||
}
|
||||
|
||||
bool capture_source_kinds(Lardon3DProjectDb *database, uint64_t capture_id,
|
||||
size_t expected_jpeg, size_t expected_raw) {
|
||||
Lardon3DProjectDbCaptureSourceAsset assets[64]{};
|
||||
size_t count = 0u;
|
||||
if (lardon3d_project_db_list_capture_source_assets(database, capture_id, 0, assets, 64,
|
||||
&count) != LARDON3D_PROJECT_DB_OK ||
|
||||
count != expected_jpeg + expected_raw)
|
||||
return false;
|
||||
size_t jpeg = 0u, raw = 0u;
|
||||
for (size_t index = 0; index < count; ++index) {
|
||||
if (assets[index].capture_id != capture_id || assets[index].asset_id == 0) return false;
|
||||
if (assets[index].source_kind == LARDON3D_DB_CAPTURE_SOURCE_JPEG) ++jpeg;
|
||||
else if (assets[index].source_kind == LARDON3D_DB_CAPTURE_SOURCE_RAW) ++raw;
|
||||
else return false;
|
||||
}
|
||||
return jpeg == expected_jpeg && raw == expected_raw;
|
||||
}
|
||||
|
||||
bool test_ingest_grouping_and_resume() {
|
||||
Fixture fixture;
|
||||
CHECK(fixture.open());
|
||||
|
|
@ -181,7 +199,8 @@ bool test_ingest_grouping_and_resume() {
|
|||
&jpeg_options, &paired) == LARDON3D_ACQUISITION_INGEST_OK);
|
||||
CHECK(paired.group_count == 1 && paired.groups[0].basis == LARDON3D_ACQUISITION_GROUP_STRONG &&
|
||||
paired.groups[0].source_count == 2 && capture_count(strong.database, strong.scanset.scanset_id) == 1 &&
|
||||
capture_assets(strong.database, paired.groups[0].capture_id, 2));
|
||||
capture_assets(strong.database, paired.groups[0].capture_id, 2) &&
|
||||
capture_source_kinds(strong.database, paired.groups[0].capture_id, 1, 1));
|
||||
|
||||
/* A competing equal strong candidate remains entirely ungrouped. */
|
||||
Fixture ambiguous;
|
||||
|
|
@ -215,6 +234,7 @@ bool test_ingest_grouping_and_resume() {
|
|||
CHECK(lardon3d_acquisition_ingest(&resumed.state, resumed.scanset.scanset_id, one, 1, &resume_options, &first) == LARDON3D_ACQUISITION_INGEST_OK && first.groups[0].capture_id == existing.capture_id);
|
||||
Lardon3DAcquisitionIngestOutput retry{};
|
||||
CHECK(lardon3d_acquisition_ingest(&resumed.state, resumed.scanset.scanset_id, one, 1, &resume_options, &retry) == LARDON3D_ACQUISITION_INGEST_OK && capture_count(resumed.database, resumed.scanset.scanset_id) == 1 && capture_assets(resumed.database, existing.capture_id, 1));
|
||||
CHECK(capture_source_kinds(resumed.database, existing.capture_id, 1, 0));
|
||||
uint64_t images = 0; CHECK(lardon3d_project_db_count_images(resumed.database, resumed.scanset.scanset_id, &images) == LARDON3D_PROJECT_DB_OK && images == 1);
|
||||
|
||||
fixture.close(); strong.close(); ambiguous.close(); resumed.close();
|
||||
|
|
@ -245,6 +265,16 @@ bool test_explicit_and_db_helper() {
|
|||
Lardon3DProjectDbImageRegisterStatus status{}; Lardon3DProjectDbImage image{};
|
||||
CHECK(lardon3d_project_db_publish_source_capture_image(fixture.database, capture.capture_id, asset.asset_id,
|
||||
"helper.JPG", helper_path, 0, 8, false, &status, &image) == LARDON3D_PROJECT_DB_OK && status == LARDON3D_PROJECT_DB_IMAGE_REGISTERED);
|
||||
CHECK(lardon3d_project_db_record_capture_source_asset(
|
||||
fixture.database, capture.capture_id, asset.asset_id,
|
||||
LARDON3D_DB_CAPTURE_SOURCE_JPEG) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_capture_source_asset(
|
||||
fixture.database, capture.capture_id, asset.asset_id,
|
||||
LARDON3D_DB_CAPTURE_SOURCE_JPEG) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_capture_source_asset(
|
||||
fixture.database, capture.capture_id, asset.asset_id,
|
||||
LARDON3D_DB_CAPTURE_SOURCE_RAW) == LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||
CHECK(capture_source_kinds(fixture.database, capture.capture_id, 1, 0));
|
||||
uint64_t selected = 0;
|
||||
CHECK(lardon3d_project_db_get_selected_capture_image(fixture.database, capture.capture_id, &selected) == LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||
CHECK(lardon3d_project_db_publish_source_capture_image(fixture.database, capture.capture_id, asset.asset_id,
|
||||
|
|
|
|||
167
tests/test_calibration_bootstrap.c
Normal file
167
tests/test_calibration_bootstrap.c
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
#include <openssl/evp.h>
|
||||
#include <sqlite3.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <lardon3d/calibration_bootstrap.h>
|
||||
|
||||
#define CHECK(condition) do { if (!(condition)) { \
|
||||
fprintf(stderr, "calibration bootstrap failure at line %d: %s\n", __LINE__, #condition); \
|
||||
return false; } } while (0)
|
||||
|
||||
enum { ARTIFACT_SIZE = 292 };
|
||||
|
||||
static bool sql(const char *path, const char *text) {
|
||||
sqlite3 *connection = NULL;
|
||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||
int code = sqlite3_exec(connection, text, NULL, NULL, NULL);
|
||||
return sqlite3_close(connection) == SQLITE_OK && code == SQLITE_OK;
|
||||
}
|
||||
|
||||
static void put_u32(unsigned char *bytes, size_t *offset, uint32_t value) {
|
||||
for (size_t index = 0; index < 4; ++index) bytes[(*offset)++] = (unsigned char)(value >> (8 * index));
|
||||
}
|
||||
|
||||
static void put_u64(unsigned char *bytes, size_t *offset, uint64_t value) {
|
||||
for (size_t index = 0; index < 8; ++index) bytes[(*offset)++] = (unsigned char)(value >> (8 * index));
|
||||
}
|
||||
|
||||
static void put_f64(unsigned char *bytes, size_t *offset, double value) {
|
||||
uint64_t bits;
|
||||
memcpy(&bits, &value, sizeof(bits));
|
||||
put_u64(bytes, offset, bits);
|
||||
}
|
||||
|
||||
static bool digest(const unsigned char *bytes, size_t size, unsigned char output[32]) {
|
||||
unsigned int output_size = 0;
|
||||
return EVP_Digest(bytes, size, output, &output_size, EVP_sha256(), NULL) == 1 &&
|
||||
output_size == 32;
|
||||
}
|
||||
|
||||
static void artifact(unsigned char bytes[ARTIFACT_SIZE]) {
|
||||
memset(bytes, 0, ARTIFACT_SIZE);
|
||||
size_t offset = 0;
|
||||
memcpy(bytes + offset, "L3DCALB1", 8); offset += 8;
|
||||
put_u32(bytes, &offset, 1); put_u32(bytes, &offset, 1);
|
||||
put_u32(bytes, &offset, 1); put_u32(bytes, &offset, 1);
|
||||
/* Four distinct nonzero evidence digests stand for executable,
|
||||
* configuration, initialization and validation evidence. */
|
||||
for (unsigned int evidence = 1; evidence <= 4; ++evidence) {
|
||||
memset(bytes + offset, (int)evidence, 32);
|
||||
offset += 32;
|
||||
}
|
||||
put_u64(bytes, &offset, 1);
|
||||
memset(bytes + offset, 0x11, 32); offset += 32;
|
||||
put_u32(bytes, &offset, 4000); put_u32(bytes, &offset, 2250);
|
||||
put_f64(bytes, &offset, 3072.6); put_f64(bytes, &offset, 3053.54);
|
||||
put_f64(bytes, &offset, 1969.56); put_f64(bytes, &offset, 1118.52);
|
||||
put_f64(bytes, &offset, 0.0452928); put_f64(bytes, &offset, -0.0377292);
|
||||
put_f64(bytes, &offset, 0.00479862); put_f64(bytes, &offset, 0.00672575);
|
||||
put_u32(bytes, &offset, 56); put_u32(bytes, &offset, 1000);
|
||||
put_f64(bytes, &offset, 0.4); put_f64(bytes, &offset, 0.000001);
|
||||
put_u32(bytes, &offset, 15);
|
||||
}
|
||||
|
||||
static bool run(void) {
|
||||
char directory[] = "/tmp/lardon3d-calibration-bootstrap-XXXXXX";
|
||||
CHECK(mkdtemp(directory) != NULL);
|
||||
char path[512];
|
||||
CHECK(snprintf(path, sizeof(path), "%s/project.db", directory) > 0);
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
lardon3d_project_db_close(database);
|
||||
/* This fixture establishes the already-frozen operational mappings. The
|
||||
* importer may validate them but must never infer them from equal IDs. */
|
||||
CHECK(sql(path,
|
||||
"INSERT INTO scansets(scanset_id,name,created_at,updated_at) VALUES(1,'scope',1,1);"
|
||||
"INSERT INTO tasks VALUES(1,'quality','photo_quality.triage',1,5,5,100,1,0,0,0,0,1);"
|
||||
"INSERT INTO tasks VALUES(2,'campaign','acquisition_campaign.run',1,5,5,100,1,0,0,0,0,1);"
|
||||
"INSERT INTO photo_quality_triage_tasks VALUES(1,1,2,1,X'01');"
|
||||
"INSERT INTO photo_quality_triage_results VALUES(1,1,0,1,0,1,0,100,100,100,100,"
|
||||
"1.0,1.0,0.0,0.0,1.0,1.0,0.0,'GOOD');"
|
||||
"INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02');"
|
||||
"INSERT INTO captures(capture_id,scanset_id,created_at) VALUES(1,1,1);"
|
||||
"INSERT INTO acquisition_campaign_captures VALUES(2,2,1);"
|
||||
"INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) VALUES"
|
||||
"(1,X'1111111111111111111111111111111111111111111111111111111111111111',"
|
||||
"'assets/images/11/1111111111111111111111111111111111111111111111111111111111111111',"
|
||||
"1,1,1);"
|
||||
"INSERT INTO images(image_id,scanset_id,asset_id,original_name,source_path,imported_at) "
|
||||
"VALUES(1,1,1,'selected.jpg','/source/selected.jpg',1);"
|
||||
"INSERT INTO capture_images VALUES(1,1);"));
|
||||
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DProjectDbSelectedExecutionItem item = {
|
||||
.item_index = 0,
|
||||
.quality_group_id = 1,
|
||||
.campaign_group_id = 2,
|
||||
.capture_id = 1,
|
||||
.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE};
|
||||
Lardon3DProjectDbSelectedExecution execution;
|
||||
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &item, 1, 10,
|
||||
&execution) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_selected_representation(database, execution.execution_id,
|
||||
0, 1, 1) == LARDON3D_PROJECT_DB_OK);
|
||||
|
||||
unsigned char bytes[ARTIFACT_SIZE], hash[32], wrong_hash[32] = {0};
|
||||
artifact(bytes);
|
||||
CHECK(digest(bytes, sizeof(bytes), hash));
|
||||
Lardon3DCalibrationBootstrapOutput output;
|
||||
CHECK(lardon3d_calibration_bootstrap_import(database, execution.execution_id, bytes,
|
||||
sizeof(bytes), wrong_hash, &output) ==
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_PROVENANCE_MISMATCH);
|
||||
|
||||
bytes[160] ^= 1u;
|
||||
CHECK(digest(bytes, sizeof(bytes), wrong_hash));
|
||||
CHECK(lardon3d_calibration_bootstrap_import(database, execution.execution_id, bytes,
|
||||
sizeof(bytes), wrong_hash, &output) ==
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_SELECTION_CONFLICT);
|
||||
artifact(bytes);
|
||||
bytes[8] = 2;
|
||||
CHECK(digest(bytes, sizeof(bytes), wrong_hash));
|
||||
CHECK(lardon3d_calibration_bootstrap_import(database, execution.execution_id, bytes,
|
||||
sizeof(bytes), wrong_hash, &output) ==
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_MALFORMED_ARTIFACT);
|
||||
artifact(bytes);
|
||||
bytes[ARTIFACT_SIZE - 4] = 14;
|
||||
CHECK(digest(bytes, sizeof(bytes), wrong_hash));
|
||||
CHECK(lardon3d_calibration_bootstrap_import(database, execution.execution_id, bytes,
|
||||
sizeof(bytes), wrong_hash, &output) ==
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_MALFORMED_ARTIFACT);
|
||||
|
||||
artifact(bytes);
|
||||
Lardon3DCalibrationBootstrapResult imported = lardon3d_calibration_bootstrap_import(
|
||||
database, execution.execution_id, bytes, sizeof(bytes), hash, &output);
|
||||
if (imported != LARDON3D_CALIBRATION_BOOTSTRAP_OK)
|
||||
fprintf(stderr, "valid artifact import result=%d\n", (int)imported);
|
||||
CHECK(imported == LARDON3D_CALIBRATION_BOOTSTRAP_OK);
|
||||
CHECK(output.calibration_count == 1 && output.scope.scope_id > 0 &&
|
||||
memcmp(output.artifact_sha256, hash, 32) == 0);
|
||||
Lardon3DSparseCalibrationMember member;
|
||||
size_t count = 0;
|
||||
uint64_t next = 0;
|
||||
CHECK(lardon3d_sparse_calibration_scope_list_members(database, output.scope.scope_id, 0,
|
||||
&member, 1, &count, &next) == LARDON3D_PROJECT_DB_OK && count == 1 &&
|
||||
member.image_id == 1);
|
||||
Lardon3DSparseCalibration calibration;
|
||||
CHECK(lardon3d_sparse_calibration_load(database, member.calibration_id, &calibration) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
calibration.provenance_kind == LARDON3D_SPARSE_SFM_PROVENANCE_IMPORTED_TRUSTED &&
|
||||
memcmp(calibration.provenance_fingerprint, hash, 32) == 0);
|
||||
CHECK(lardon3d_calibration_bootstrap_import(database, execution.execution_id, bytes,
|
||||
sizeof(bytes), hash, &output) == LARDON3D_CALIBRATION_BOOTSTRAP_OK);
|
||||
CHECK(lardon3d_project_db_load_selected_execution(database, execution.execution_id,
|
||||
&execution) == LARDON3D_PROJECT_DB_OK &&
|
||||
execution.stage == LARDON3D_SELECTED_EXECUTION_READY &&
|
||||
execution.calibration_scope_id == output.scope.scope_id);
|
||||
|
||||
lardon3d_project_db_close(database);
|
||||
CHECK(unlink(path) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(void) { return run() ? EXIT_SUCCESS : EXIT_FAILURE; }
|
||||
|
|
@ -116,7 +116,7 @@ static bool create_future_database(const char *path) {
|
|||
sqlite3_exec(
|
||||
connection,
|
||||
"CREATE TABLE metadata(key TEXT PRIMARY KEY,value INTEGER NOT NULL);"
|
||||
"INSERT INTO metadata VALUES('schema_version',22);",
|
||||
"INSERT INTO metadata VALUES('schema_version',23);",
|
||||
NULL, NULL, NULL) == SQLITE_OK;
|
||||
return sqlite3_close(connection) == SQLITE_OK && ok;
|
||||
}
|
||||
|
|
@ -887,7 +887,7 @@ static bool run_test(void) {
|
|||
char v10_path[512], failed_v11_path[512];
|
||||
char v13_path[512], true_v14_path[512], failed_v14_path[512],
|
||||
failed_v15_path[512], true_v15_path[512], v17_path[512],
|
||||
failed_v18_path[512], failed_v19_path[512];
|
||||
failed_v18_path[512], failed_v19_path[512], raw_missing_table_path[512];
|
||||
CHECK(snprintf(database_path, sizeof(database_path), "%s/project.db",
|
||||
directory) > 0);
|
||||
CHECK(snprintf(artifact_path, sizeof(artifact_path), "%s/artifact.bin",
|
||||
|
|
@ -935,12 +935,14 @@ static bool run_test(void) {
|
|||
"%s/failed-v18.db", directory) > 0);
|
||||
CHECK(snprintf(failed_v19_path, sizeof(failed_v19_path),
|
||||
"%s/failed-v19.db", directory) > 0);
|
||||
CHECK(snprintf(raw_missing_table_path, sizeof(raw_missing_table_path),
|
||||
"%s/raw-missing-table.db", directory) > 0);
|
||||
|
||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(database && lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(database && lardon3d_project_db_schema_version(database) == 22);
|
||||
bool legacy_pending = true;
|
||||
CHECK(lardon3d_project_db_legacy_catalog_pending(database, &legacy_pending) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
|
|
@ -1499,7 +1501,7 @@ static bool run_test(void) {
|
|||
database = NULL;
|
||||
CHECK(query_integer(database_path,
|
||||
"SELECT value FROM metadata WHERE key='schema_version'",
|
||||
21));
|
||||
22));
|
||||
CHECK(query_integer(database_path,
|
||||
"SELECT count(*) FROM tasks WHERE task_id=1", 1));
|
||||
|
||||
|
|
@ -1521,13 +1523,15 @@ static bool run_test(void) {
|
|||
"created_at) VALUES(1,1,1,1,1,zeroblob(32),1,2,1,0,0,1);"
|
||||
"DROP TABLE incremental_reconstruction_tasks;"
|
||||
"DROP TABLE incremental_reconstructions;"
|
||||
"DROP TABLE selected_execution_items;DROP TABLE selected_executions;"
|
||||
"DROP TABLE photo_quality_triage_results;DROP TABLE photo_quality_triage_tasks;"
|
||||
"DROP TABLE acquisition_campaign_captures;DROP TABLE acquisition_campaign_tasks;"
|
||||
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
|
||||
"DROP TABLE capture_images;DROP TABLE captures;"
|
||||
"UPDATE metadata SET value=17 WHERE key='schema_version';"));
|
||||
CHECK(lardon3d_project_db_open(v17_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(query_integer(v17_path,
|
||||
|
|
@ -1551,6 +1555,8 @@ static bool run_test(void) {
|
|||
database = NULL;
|
||||
CHECK(execute_test_sql(
|
||||
failed_v19_path,
|
||||
"DROP TABLE selected_execution_items;DROP TABLE selected_executions;"
|
||||
"DROP TABLE photo_quality_triage_results;DROP TABLE photo_quality_triage_tasks;"
|
||||
"DROP TABLE acquisition_campaign_captures;DROP TABLE acquisition_campaign_tasks;"
|
||||
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
|
||||
"DROP TABLE capture_images;DROP TABLE captures;"
|
||||
|
|
@ -1566,13 +1572,15 @@ static bool run_test(void) {
|
|||
"name='captures'", 0));
|
||||
CHECK(lardon3d_project_db_open(failed_v19_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(execute_test_sql(
|
||||
failed_v18_path,
|
||||
"DROP TABLE incremental_reconstruction_tasks;"
|
||||
"DROP TABLE incremental_reconstructions;"
|
||||
"DROP TABLE selected_execution_items;DROP TABLE selected_executions;"
|
||||
"DROP TABLE photo_quality_triage_results;DROP TABLE photo_quality_triage_tasks;"
|
||||
"DROP TABLE acquisition_campaign_captures;DROP TABLE acquisition_campaign_tasks;"
|
||||
"DROP TABLE asset_derivations;DROP TABLE capture_selections;DROP TABLE capture_assets;"
|
||||
"DROP TABLE capture_images;DROP TABLE captures;"
|
||||
|
|
@ -1618,7 +1626,7 @@ static bool run_test(void) {
|
|||
CHECK(create_v1_database(legacy_path));
|
||||
CHECK(lardon3d_project_db_open(legacy_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
CHECK(lardon3d_project_db_get_project(database, &loaded_project) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
strcmp(loaded_project.stable_id, "legacy-project") == 0);
|
||||
|
|
@ -1633,7 +1641,17 @@ static bool run_test(void) {
|
|||
database = NULL;
|
||||
CHECK(query_integer(legacy_path,
|
||||
"SELECT value FROM metadata WHERE key='schema_version'",
|
||||
21));
|
||||
22));
|
||||
CHECK(create_v1_database(raw_missing_table_path));
|
||||
CHECK(lardon3d_project_db_open(raw_missing_table_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(execute_test_sql(raw_missing_table_path,
|
||||
"DROP TABLE raw_development_tasks;"));
|
||||
CHECK(lardon3d_project_db_open(raw_missing_table_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_CORRUPT);
|
||||
CHECK(database == NULL);
|
||||
|
||||
CHECK(create_v1_database(failed_migration_path));
|
||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V2", "1", 1) == 0);
|
||||
|
|
@ -1664,7 +1682,7 @@ static bool run_test(void) {
|
|||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(query_integer(
|
||||
v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 21));
|
||||
v2_path, "SELECT value FROM metadata WHERE key='schema_version'", 22));
|
||||
|
||||
CHECK(create_v2_database(failed_v3_migration_path));
|
||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V3", "1", 1) == 0);
|
||||
|
|
@ -1698,7 +1716,7 @@ static bool run_test(void) {
|
|||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(query_integer(
|
||||
v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 21));
|
||||
v3_path, "SELECT value FROM metadata WHERE key='schema_version'", 22));
|
||||
|
||||
CHECK(create_v3_database(failed_v4_path));
|
||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V4", "1", 1) == 0);
|
||||
|
|
@ -1720,7 +1738,7 @@ static bool run_test(void) {
|
|||
fprintf(stderr, "Migration v4 (%d): %s\n", (int)v4_result, error);
|
||||
}
|
||||
CHECK(v4_result == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
CHECK(lardon3d_project_db_load_task(database, 9, &task) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_artifact(database, "legacy-artifact",
|
||||
|
|
@ -1775,7 +1793,7 @@ static bool run_test(void) {
|
|||
error);
|
||||
}
|
||||
CHECK(retry_v7 == LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -1785,7 +1803,7 @@ static bool run_test(void) {
|
|||
5));
|
||||
CHECK(lardon3d_project_db_open(direct_v5_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -1794,11 +1812,11 @@ static bool run_test(void) {
|
|||
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 7));
|
||||
CHECK(lardon3d_project_db_open(v8_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(query_integer(
|
||||
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 21));
|
||||
v8_path, "SELECT value FROM metadata WHERE key='schema_version'", 22));
|
||||
|
||||
CHECK(create_v7_database(failed_v8_path));
|
||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V8", "1", 1) == 0);
|
||||
|
|
@ -1815,7 +1833,7 @@ static bool run_test(void) {
|
|||
0));
|
||||
CHECK(lardon3d_project_db_open(failed_v8_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -1834,11 +1852,11 @@ static bool run_test(void) {
|
|||
0));
|
||||
CHECK(lardon3d_project_db_open(v10_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(query_integer(
|
||||
v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 21));
|
||||
v10_path, "SELECT value FROM metadata WHERE key='schema_version'", 22));
|
||||
CHECK(
|
||||
query_integer(v10_path,
|
||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||
|
|
@ -1865,7 +1883,7 @@ static bool run_test(void) {
|
|||
1));
|
||||
CHECK(lardon3d_project_db_open(failed_v11_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -1879,11 +1897,11 @@ static bool run_test(void) {
|
|||
0));
|
||||
CHECK(lardon3d_project_db_open(v13_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(query_integer(
|
||||
v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 21));
|
||||
v13_path, "SELECT value FROM metadata WHERE key='schema_version'", 22));
|
||||
CHECK(
|
||||
query_integer(v13_path,
|
||||
"SELECT count(*) FROM sqlite_master WHERE type='table' AND "
|
||||
|
|
@ -1906,7 +1924,7 @@ static bool run_test(void) {
|
|||
true_v14_path);
|
||||
}
|
||||
CHECK(true_v14_result == LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(
|
||||
|
|
@ -2021,7 +2039,7 @@ static bool run_test(void) {
|
|||
if (true_v15_result != LARDON3D_PROJECT_DB_OK)
|
||||
fprintf(stderr, "true v15 upgrade: %d %s\n", true_v15_result, error);
|
||||
CHECK(true_v15_result == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
Lardon3DProjectDbProject migrated_project;
|
||||
CHECK(lardon3d_project_db_get_project(database, &migrated_project) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
|
|
@ -2067,7 +2085,7 @@ static bool run_test(void) {
|
|||
database = NULL;
|
||||
CHECK(lardon3d_project_db_open(database_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
CHECK(schema_compare(database_path, true_v15_path, true));
|
||||
|
|
@ -2102,7 +2120,7 @@ static bool run_test(void) {
|
|||
0));
|
||||
CHECK(lardon3d_project_db_open(true_v15_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 21);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -2121,7 +2139,7 @@ static bool run_test(void) {
|
|||
0));
|
||||
CHECK(lardon3d_project_db_open(failed_v14_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -2140,7 +2158,7 @@ static bool run_test(void) {
|
|||
0));
|
||||
CHECK(lardon3d_project_db_open(failed_v15_path, &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
lardon3d_project_db_schema_version(database) == 21);
|
||||
lardon3d_project_db_schema_version(database) == 22);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
|
|
@ -2612,6 +2630,7 @@ static bool run_test(void) {
|
|||
CHECK(unlink(v17_path) == 0);
|
||||
CHECK(unlink(failed_v18_path) == 0);
|
||||
CHECK(unlink(failed_v19_path) == 0);
|
||||
CHECK(unlink(raw_missing_table_path) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -283,6 +283,18 @@ static bool run_test() {
|
|||
&state, capture.capture_id, corrupt_path, 0, 1, &output);
|
||||
CHECK(corrupt == LARDON3D_RAW_DEVELOPMENT_UNSUPPORTED_RAW
|
||||
|| corrupt == LARDON3D_RAW_DEVELOPMENT_CORRUPT_RAW);
|
||||
CHECK(output.source_asset.asset_id != 0);
|
||||
const uint64_t corrupt_asset_id = output.source_asset.asset_id;
|
||||
CHECK(lardon3d_raw_develop_asset_to_capture(
|
||||
&state, capture.capture_id, corrupt_asset_id, 0, 1, &output) ==
|
||||
LARDON3D_RAW_DEVELOPMENT_CONSTRAINT);
|
||||
CHECK(lardon3d_project_db_record_capture_source_asset(
|
||||
db, capture.capture_id, corrupt_asset_id,
|
||||
LARDON3D_DB_CAPTURE_SOURCE_RAW) == LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DRawDevelopmentResult explicit_corrupt = lardon3d_raw_develop_asset_to_capture(
|
||||
&state, capture.capture_id, corrupt_asset_id, 0, 1, &output);
|
||||
CHECK(explicit_corrupt == LARDON3D_RAW_DEVELOPMENT_UNSUPPORTED_RAW ||
|
||||
explicit_corrupt == LARDON3D_RAW_DEVELOPMENT_CORRUPT_RAW);
|
||||
uint64_t selected = 0;
|
||||
CHECK(lardon3d_project_db_get_selected_capture_image(db, capture.capture_id, &selected)
|
||||
== LARDON3D_PROJECT_DB_NOT_FOUND);
|
||||
|
|
|
|||
196
tests/test_raw_development_task.cpp
Normal file
196
tests/test_raw_development_task.cpp
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sqlite3.h>
|
||||
#include <unistd.h>
|
||||
|
||||
extern "C" {
|
||||
#include <lardon3d/image_catalog.h>
|
||||
#include <lardon3d/project.h>
|
||||
#include <lardon3d/raw_development_task.h>
|
||||
#include <lardon3d/resource_governor.h>
|
||||
}
|
||||
|
||||
extern "C" const Lardon3DTaskKindRegistry *lardon3d_task_kind_registry_production(void) {
|
||||
static const Lardon3DTaskKindRegistry registry{};
|
||||
return ®istry;
|
||||
}
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
std::fprintf(stderr, "Failure line %d: %s\n", __LINE__, #condition); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static bool write_file(const std::string &path, const char *bytes) {
|
||||
int descriptor = open(path.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0600);
|
||||
if (descriptor < 0) return false;
|
||||
size_t size = std::strlen(bytes);
|
||||
bool ok = write(descriptor, bytes, size) == static_cast<ssize_t>(size);
|
||||
return close(descriptor) == 0 && ok;
|
||||
}
|
||||
|
||||
int main() {
|
||||
char directory_template[] = "/tmp/lardon3d-raw-task-XXXXXX";
|
||||
char *directory = mkdtemp(directory_template);
|
||||
CHECK(directory != nullptr);
|
||||
std::string root(directory);
|
||||
std::string database_path = root + "/project.lardon3d";
|
||||
CHECK(std::filesystem::create_directories(root + "/.lardon3d/checkpoints"));
|
||||
CHECK(std::filesystem::create_directories(root + "/assets/images"));
|
||||
std::string raw_path = root + "/source.arw";
|
||||
std::string png_path = root + "/derived.png";
|
||||
CHECK(write_file(raw_path, "explicit raw bytes"));
|
||||
CHECK(write_file(png_path, "derived png bytes"));
|
||||
|
||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
||||
Lardon3DProjectDb *database = nullptr;
|
||||
CHECK(lardon3d_project_db_open(database_path.c_str(), &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DProjectDbScanSet scanset{};
|
||||
Lardon3DProjectDbCapture capture{};
|
||||
CHECK(lardon3d_project_db_create_scanset(database, "raw-task", &scanset) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_create_capture(database, scanset.scanset_id, 7, &capture) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
|
||||
Lardon3DAppState state{};
|
||||
lardon3d_app_state_init(&state);
|
||||
state.project_loaded = true;
|
||||
state.project_db = database;
|
||||
std::snprintf(state.project_path, sizeof(state.project_path), "%s", root.c_str());
|
||||
state.hardware_profile.logical_cpu_count = 8;
|
||||
state.hardware_profile.page_size_bytes = 4096;
|
||||
state.hardware_profile.memory_total_bytes = UINT64_C(8) << 30;
|
||||
std::snprintf(state.hardware_profile.cpu_architecture,
|
||||
sizeof(state.hardware_profile.cpu_architecture), "test");
|
||||
Lardon3DResourcePolicy policy{};
|
||||
policy.maximum_cpu_load_ratio = 1.0;
|
||||
policy.maximum_io_pressure_avg10 = 100.0;
|
||||
policy.io_slot_capacity = 1;
|
||||
state.resource_governor = lardon3d_resource_governor_create(&state.hardware_profile, &policy);
|
||||
CHECK(state.resource_governor != nullptr);
|
||||
|
||||
Lardon3DProjectDbImageAsset raw_asset{};
|
||||
Lardon3DProjectDbImageAsset derived_asset{};
|
||||
CHECK(lardon3d_image_catalog_publish_asset_file(
|
||||
&state, raw_path.c_str(), 7, UINT64_MAX, &raw_asset) ==
|
||||
LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED);
|
||||
CHECK(lardon3d_project_db_record_capture_source_asset(
|
||||
database, capture.capture_id, raw_asset.asset_id,
|
||||
LARDON3D_DB_CAPTURE_SOURCE_RAW) == LARDON3D_PROJECT_DB_OK);
|
||||
|
||||
uint64_t task_id = 0;
|
||||
Lardon3DTask *task = lardon3d_project_create_raw_development_task(
|
||||
&state, capture.capture_id, raw_asset.asset_id, &task_id);
|
||||
CHECK(task != nullptr && task_id != 0);
|
||||
Lardon3DProjectDbTask generic{};
|
||||
Lardon3DProjectDbRawDevelopmentTask typed{};
|
||||
CHECK(lardon3d_project_db_load_task(database, task_id, &generic) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(generic.has_checkpoint && generic.has_task_kind &&
|
||||
std::strcmp(generic.task_kind, LARDON3D_RAW_DEVELOPMENT_TASK_KIND) == 0);
|
||||
CHECK(lardon3d_project_db_load_raw_development_task(database, task_id, &typed) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(typed.capture_id == capture.capture_id && typed.source_asset_id == raw_asset.asset_id &&
|
||||
typed.phase == LARDON3D_RAW_DEVELOPMENT_TASK_PENDING && !typed.has_image);
|
||||
|
||||
Lardon3DTaskDurableSnapshot snapshot{};
|
||||
CHECK(lardon3d_task_durable_snapshot(task, &snapshot));
|
||||
Lardon3DTaskReconstructionContext reconstruction{
|
||||
root.c_str(), database, state.resource_governor, nullptr};
|
||||
Lardon3DTaskKindBinding binding{};
|
||||
CHECK(lardon3d_raw_development_task_reconstruct(&snapshot, &reconstruction, &binding));
|
||||
CHECK(binding.callback != nullptr && binding.userdata != nullptr &&
|
||||
binding.userdata_destroy != nullptr && binding.finished_callback != nullptr);
|
||||
binding.userdata_destroy(binding.userdata);
|
||||
|
||||
CHECK(lardon3d_image_catalog_publish_asset_file(
|
||||
&state, png_path.c_str(), 7, UINT64_MAX, &derived_asset) ==
|
||||
LARDON3D_IMAGE_CATALOG_ASSET_PUBLISHED);
|
||||
Lardon3DProjectDbAssetDerivation derivation{};
|
||||
derivation.parent_asset_id = raw_asset.asset_id;
|
||||
derivation.child_asset_id = derived_asset.asset_id;
|
||||
derivation.kind = LARDON3D_DB_ASSET_DERIVATION_GENERIC_VERSIONED;
|
||||
derivation.version = 1;
|
||||
derivation.has_producer_task = true;
|
||||
derivation.producer_task_id = task_id;
|
||||
derivation.created_at = 7;
|
||||
CHECK(lardon3d_project_db_record_asset_derivation(database, &derivation) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DProjectDbImageRegisterStatus image_status{};
|
||||
Lardon3DProjectDbImage image{};
|
||||
CHECK(lardon3d_project_db_publish_derived_capture_image(
|
||||
database, capture.capture_id, derived_asset.asset_id, "derived.png", png_path.c_str(),
|
||||
task_id, 7, &image_status, &image) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_task_set_progress(task, 100, "published"));
|
||||
typed.phase = LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED;
|
||||
typed.has_image = true;
|
||||
typed.image_id = image.image_id;
|
||||
CHECK(lardon3d_project_checkpoint_raw_development_task(&state, task, &typed) ==
|
||||
LARDON3D_PROJECT_TASK_CHECKPOINT_OK);
|
||||
Lardon3DProjectDbRawDevelopmentTask published{};
|
||||
CHECK(lardon3d_project_db_load_raw_development_task(database, task_id, &published) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(published.phase == LARDON3D_RAW_DEVELOPMENT_TASK_PUBLISHED &&
|
||||
published.image_id == image.image_id);
|
||||
|
||||
Lardon3DProjectDbRawDevelopmentTask conflict = published;
|
||||
conflict.source_asset_id = derived_asset.asset_id;
|
||||
CHECK(lardon3d_project_checkpoint_raw_development_task(&state, task, &conflict) ==
|
||||
LARDON3D_PROJECT_TASK_CHECKPOINT_DB_ERROR);
|
||||
CHECK(lardon3d_project_db_load_raw_development_task(database, task_id, &published) ==
|
||||
LARDON3D_PROJECT_DB_OK &&
|
||||
published.source_asset_id == raw_asset.asset_id && published.image_id == image.image_id);
|
||||
|
||||
CHECK(lardon3d_project_db_record_raw_development_task(
|
||||
database, &snapshot, "test.work", 1, nullptr, &typed,
|
||||
7) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
CHECK(lardon3d_project_db_record_raw_development_task(
|
||||
database, &snapshot, LARDON3D_RAW_DEVELOPMENT_TASK_KIND, 2, nullptr, &typed,
|
||||
7) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT);
|
||||
|
||||
sqlite3 *raw_database = nullptr;
|
||||
char update_mismatch_kind[128];
|
||||
std::snprintf(update_mismatch_kind, sizeof(update_mismatch_kind),
|
||||
"UPDATE tasks SET task_kind='test.work',task_kind_version=1 WHERE task_id=%llu",
|
||||
(unsigned long long)task_id);
|
||||
CHECK(sqlite3_open(database_path.c_str(), &raw_database) == SQLITE_OK);
|
||||
CHECK(sqlite3_exec(raw_database, update_mismatch_kind, nullptr, nullptr,
|
||||
nullptr) == SQLITE_OK);
|
||||
CHECK(lardon3d_project_db_load_raw_development_task(database, task_id, &published) ==
|
||||
LARDON3D_PROJECT_DB_CORRUPT);
|
||||
char update_recoverable_kind[128];
|
||||
std::snprintf(update_recoverable_kind, sizeof(update_recoverable_kind),
|
||||
"UPDATE tasks SET task_kind='raw.develop',task_kind_version=1 "
|
||||
"WHERE task_id=%llu",
|
||||
(unsigned long long)task_id);
|
||||
CHECK(sqlite3_exec(raw_database, update_recoverable_kind, nullptr, nullptr,
|
||||
nullptr) == SQLITE_OK);
|
||||
CHECK(sqlite3_close(raw_database) == SQLITE_OK);
|
||||
|
||||
lardon3d_task_destroy(task);
|
||||
lardon3d_resource_governor_destroy(state.resource_governor);
|
||||
lardon3d_project_db_close(database);
|
||||
|
||||
/* This fixture bypasses schema CHECK enforcement deliberately to prove that
|
||||
* recovery treats malformed durable phase as corruption, not as pending. */
|
||||
CHECK(sqlite3_open(database_path.c_str(), &raw_database) == SQLITE_OK);
|
||||
CHECK(sqlite3_exec(raw_database, "PRAGMA ignore_check_constraints=ON", nullptr, nullptr,
|
||||
nullptr) == SQLITE_OK);
|
||||
CHECK(sqlite3_exec(raw_database, "UPDATE raw_development_tasks SET phase=9", nullptr, nullptr,
|
||||
nullptr) == SQLITE_OK);
|
||||
CHECK(sqlite3_close(raw_database) == SQLITE_OK);
|
||||
database = nullptr;
|
||||
CHECK(lardon3d_project_db_open(database_path.c_str(), &database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_raw_development_task(database, task_id, &published) ==
|
||||
LARDON3D_PROJECT_DB_CORRUPT);
|
||||
lardon3d_project_db_close(database);
|
||||
CHECK(std::filesystem::remove_all(root) > 0);
|
||||
return 0;
|
||||
}
|
||||
171
tests/test_selected_execution.c
Normal file
171
tests/test_selected_execution.c
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
#include <sqlite3.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <lardon3d/project_db.h>
|
||||
|
||||
#define CHECK(condition) do { if (!(condition)) { \
|
||||
fprintf(stderr, "selected execution failure at line %d: %s\n", __LINE__, #condition); \
|
||||
return false; } } while (0)
|
||||
|
||||
static bool sql(const char *path, const char *text) {
|
||||
sqlite3 *connection = NULL;
|
||||
if (sqlite3_open(path, &connection) != SQLITE_OK) return false;
|
||||
int code = sqlite3_exec(connection, text, NULL, NULL, NULL);
|
||||
return sqlite3_close(connection) == SQLITE_OK && code == SQLITE_OK;
|
||||
}
|
||||
|
||||
static bool seed_v21(const char *path) {
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
lardon3d_project_db_close(database);
|
||||
/* The two group namespaces intentionally use different values. This fixture
|
||||
proves that v22 retains the explicit bridge rather than equating IDs. */
|
||||
return sql(path,
|
||||
"INSERT INTO scansets(scanset_id,name,created_at,updated_at) VALUES(1,'scope',1,1);"
|
||||
"INSERT INTO tasks VALUES(1,'quality','photo_quality.triage',1,5,5,100,1,0,0,0,0,1);"
|
||||
"INSERT INTO tasks VALUES(2,'campaign','acquisition_campaign.run',1,5,5,100,1,0,0,0,0,1);"
|
||||
"INSERT INTO photo_quality_triage_tasks VALUES(1,1,2,1,X'01');"
|
||||
"INSERT INTO photo_quality_triage_results VALUES(1,1,0,1,0,1,0,100,100,100,100,"
|
||||
"1.0,1.0,0.0,0.0,1.0,1.0,0.0,'GOOD');"
|
||||
"INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02');"
|
||||
"INSERT INTO captures(capture_id,scanset_id,created_at) VALUES(1,1,1);"
|
||||
"INSERT INTO acquisition_campaign_captures VALUES(2,2,1);"
|
||||
"INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) "
|
||||
"VALUES(1,zeroblob(32),'assets/images/00/test',1,1,1);"
|
||||
"INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) "
|
||||
"VALUES(2,randomblob(32),'assets/images/00/raw',1,1,1);"
|
||||
"INSERT INTO capture_assets(capture_id,asset_id,role) VALUES(1,2,1);"
|
||||
"INSERT INTO capture_source_assets(capture_id,asset_id,source_kind) VALUES(1,2,2);"
|
||||
"INSERT INTO images(image_id,scanset_id,asset_id,original_name,source_path,imported_at) "
|
||||
"VALUES(1,1,1,'selected.jpg','/source/selected.jpg',1);"
|
||||
"INSERT INTO capture_images VALUES(1,1);"
|
||||
"INSERT INTO sparse_calibrations VALUES(1,randomblob(32),1,1,100,100,50,50,50,50,0,0,0,0,"
|
||||
"2,randomblob(32));"
|
||||
"INSERT INTO sparse_calibration_scopes VALUES(1,randomblob(32),1);"
|
||||
"INSERT INTO sparse_calibration_scope_images VALUES(1,1,1);"
|
||||
"DROP TABLE selected_execution_items;DROP TABLE selected_executions;"
|
||||
"UPDATE metadata SET value=21 WHERE key='schema_version';");
|
||||
}
|
||||
|
||||
static bool run(void) {
|
||||
char directory[] = "/tmp/lardon3d-selected-XXXXXX";
|
||||
CHECK(mkdtemp(directory) != NULL);
|
||||
char path[512];
|
||||
CHECK(snprintf(path, sizeof(path), "%s/project.db", directory) > 0);
|
||||
CHECK(seed_v21(path));
|
||||
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
CHECK(setenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V22", "1", 1) == 0);
|
||||
Lardon3DProjectDbResult migration_result = lardon3d_project_db_open(path, &database, error);
|
||||
CHECK(migration_result != LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_MIGRATION_V22") == 0);
|
||||
CHECK(sql(path, "CREATE TEMP TABLE migration_check(value INTEGER);"));
|
||||
sqlite3 *raw = NULL;
|
||||
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
||||
sqlite3_stmt *query = NULL;
|
||||
CHECK(sqlite3_prepare_v2(raw, "SELECT value FROM metadata WHERE key='schema_version'", -1,
|
||||
&query, NULL) == SQLITE_OK);
|
||||
CHECK(sqlite3_step(query) == SQLITE_ROW && sqlite3_column_int(query, 0) == 21);
|
||||
sqlite3_finalize(query);
|
||||
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
||||
|
||||
migration_result = lardon3d_project_db_open(path, &database, error);
|
||||
if (migration_result != LARDON3D_PROJECT_DB_OK)
|
||||
fprintf(stderr, "retry migration result=%d error=%s\n", (int)migration_result, error);
|
||||
CHECK(migration_result == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_schema_version(database) == 22);
|
||||
Lardon3DProjectDbSelectedExecutionItem item = {
|
||||
.item_index = 0,
|
||||
.quality_group_id = 1,
|
||||
.campaign_group_id = 2,
|
||||
.capture_id = 1,
|
||||
.representation_source = LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET,
|
||||
.source_asset_id = 2};
|
||||
Lardon3DProjectDbSelectedExecution execution;
|
||||
Lardon3DProjectDbSelectedExecutionItem unattached = item;
|
||||
unattached.source_asset_id = 1;
|
||||
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &unattached, 1, 10,
|
||||
&execution) ==
|
||||
LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &item, 1, 10,
|
||||
&execution) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(execution.execution_id > 0 && execution.stage ==
|
||||
LARDON3D_SELECTED_EXECUTION_REPRESENTATIONS && execution.next_item_index == 0);
|
||||
uint64_t execution_id = execution.execution_id;
|
||||
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &item, 1, 10,
|
||||
&execution) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(execution.execution_id == execution_id);
|
||||
Lardon3DProjectDbSelectedExecutionItem conflict = item;
|
||||
conflict.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE;
|
||||
conflict.source_asset_id = 0;
|
||||
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &conflict, 1, 10,
|
||||
&execution) ==
|
||||
LARDON3D_PROJECT_DB_CONSTRAINT);
|
||||
CHECK(lardon3d_project_db_record_selected_representation(database, execution_id, 0, 1, 1) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_selected_representation(database, execution_id, 0, 1, 1) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(execution.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
|
||||
execution.next_item_index == 1 && !execution.has_calibration_scope);
|
||||
Lardon3DProjectDbSelectedExecutionItem loaded;
|
||||
CHECK(lardon3d_project_db_load_selected_execution_item(database, execution_id, 0, &loaded) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(loaded.quality_group_id == 1 && loaded.campaign_group_id == 2 &&
|
||||
loaded.capture_id == 1 &&
|
||||
loaded.representation_source == LARDON3D_SELECTED_REPRESENTATION_RAW_ASSET &&
|
||||
loaded.source_asset_id == 2 && loaded.has_image && loaded.image_id == 1);
|
||||
CHECK(lardon3d_project_db_assign_selected_calibration_scope(database, execution_id, 1) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_assign_selected_calibration_scope(database, execution_id, 1) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
|
||||
LARDON3D_PROJECT_DB_OK && execution.stage == LARDON3D_SELECTED_EXECUTION_READY &&
|
||||
execution.has_calibration_scope && execution.calibration_scope_id == 1);
|
||||
CHECK(lardon3d_project_db_create_selected_execution(database, 1, 2, &item, 1, 10,
|
||||
&execution) == LARDON3D_PROJECT_DB_OK &&
|
||||
execution.execution_id == execution_id &&
|
||||
execution.stage == LARDON3D_SELECTED_EXECUTION_READY);
|
||||
lardon3d_project_db_close(database);
|
||||
database = NULL;
|
||||
|
||||
/* CHECK constraints are bypassed deliberately to verify wide-value and
|
||||
stage/cursor corruption rejection at the public read boundary. */
|
||||
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
||||
CHECK(sqlite3_exec(raw, "PRAGMA ignore_check_constraints=ON;"
|
||||
"UPDATE selected_execution_items SET representation_source=1 "
|
||||
"WHERE execution_id=1;", NULL, NULL, NULL) == SQLITE_OK);
|
||||
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
||||
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_selected_execution_item(database, execution_id, 0, &loaded) ==
|
||||
LARDON3D_PROJECT_DB_CORRUPT);
|
||||
lardon3d_project_db_close(database);
|
||||
CHECK(sql(path, "UPDATE selected_execution_items SET representation_source=2 "
|
||||
"WHERE execution_id=1;"));
|
||||
|
||||
CHECK(sqlite3_open(path, &raw) == SQLITE_OK);
|
||||
CHECK(sqlite3_exec(raw, "PRAGMA ignore_check_constraints=ON;"
|
||||
"UPDATE selected_executions SET next_item_index=4294967296 "
|
||||
"WHERE execution_id=1;", NULL, NULL, NULL) == SQLITE_OK);
|
||||
CHECK(sqlite3_close(raw) == SQLITE_OK);
|
||||
CHECK(lardon3d_project_db_open(path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
|
||||
LARDON3D_PROJECT_DB_CORRUPT);
|
||||
lardon3d_project_db_close(database);
|
||||
CHECK(unlink(path) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(void) { return run() ? EXIT_SUCCESS : EXIT_FAILURE; }
|
||||
Loading…
Reference in a new issue