feat: publish heterogeneous calibration groups
This commit is contained in:
parent
8fee775586
commit
a77d095a85
13 changed files with 1163 additions and 25 deletions
|
|
@ -340,6 +340,7 @@ The final product contracts for these areas are being defined separately before
|
||||||
- [Track Builder](docs/architecture/track_builder.md)
|
- [Track Builder](docs/architecture/track_builder.md)
|
||||||
- [Sparse SfM](docs/architecture/sparse_sfm.md)
|
- [Sparse SfM](docs/architecture/sparse_sfm.md)
|
||||||
- [Calibration Bootstrap v1](docs/architecture/calibration_bootstrap.md)
|
- [Calibration Bootstrap v1](docs/architecture/calibration_bootstrap.md)
|
||||||
|
- [Calibration Publication v2](docs/architecture/calibration_publication_v2.md)
|
||||||
- [Calibration Science v1](docs/architecture/calibration_science_v1.md)
|
- [Calibration Science v1](docs/architecture/calibration_science_v1.md)
|
||||||
- [Calibration Solver Preflight v1](docs/architecture/calibration_solver_preflight_v1.md)
|
- [Calibration Solver Preflight v1](docs/architecture/calibration_solver_preflight_v1.md)
|
||||||
- [Photo Quality Triage](docs/architecture/photo_quality_triage.md)
|
- [Photo Quality Triage](docs/architecture/photo_quality_triage.md)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ REAL_S21_TRACKS=PASS/FROZEN
|
||||||
REAL_A6000_PRE_SFM=PASS/FROZEN
|
REAL_A6000_PRE_SFM=PASS/FROZEN
|
||||||
PRODUCT_DEFINITION_V1=PASS/FROZEN
|
PRODUCT_DEFINITION_V1=PASS/FROZEN
|
||||||
PROMPT_TREE=CURRENT
|
PROMPT_TREE=CURRENT
|
||||||
CURRENT_NEXT=CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION
|
CURRENT_NEXT=CALIBRATION_V2_WORKFLOW_READY
|
||||||
```
|
```
|
||||||
|
|
||||||
This index separates current authority, historical evidence and future product-definition work.
|
This index separates current authority, historical evidence and future product-definition work.
|
||||||
|
|
|
||||||
89
docs/architecture/calibration_publication_v2.md
Normal file
89
docs/architecture/calibration_publication_v2.md
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
# Calibration Publication v2
|
||||||
|
|
||||||
|
**PASS / FROZEN — additive heterogeneous calibration publication boundary.**
|
||||||
|
|
||||||
|
## Authority and scope
|
||||||
|
|
||||||
|
Calibration Science v2 is the frozen scientific authority. This document
|
||||||
|
defines only the additive heterogeneous publication bridge:
|
||||||
|
|
||||||
|
```text
|
||||||
|
caller-owned validated group evidence
|
||||||
|
-> Calibration Tooling v2
|
||||||
|
-> L3DCALB2
|
||||||
|
-> Calibration Bootstrap v2
|
||||||
|
-> existing SparseCalibrationScope(image_id -> calibration_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
It does not change `L3DCALB1`, Calibration Tooling v1, or Calibration Bootstrap
|
||||||
|
v1. It does not run a solver, define optical applicability thresholds, resolve
|
||||||
|
READY outside publication, or add persistence schema.
|
||||||
|
|
||||||
|
## L3DCALB2 format
|
||||||
|
|
||||||
|
The artifact is deterministic, fixed-width, and little-endian. Integers use
|
||||||
|
the stated unsigned width. Binary64 fields contain IEEE-754 bits; producers
|
||||||
|
canonicalize negative zero and importers reject non-finite values. Native
|
||||||
|
structures, padding, locale text, and trailing bytes are forbidden. The whole
|
||||||
|
artifact is at most 600,000 bytes, with 1..4,096 groups and 1..4,096 entries;
|
||||||
|
the byte-size bound may impose a lower combined maximum. SHA-256 fields must be
|
||||||
|
nonzero.
|
||||||
|
|
||||||
|
```text
|
||||||
|
magic 8 bytes = ASCII "L3DCALB2"
|
||||||
|
format_version u32 = 2
|
||||||
|
model_kind u32 = PINHOLE(1)
|
||||||
|
model_version u32 = 1
|
||||||
|
group_count u32, 1..4096
|
||||||
|
entry_count u32, 1..4096
|
||||||
|
|
||||||
|
for each group in strictly increasing
|
||||||
|
(group_identity_sha256, group_version) order:
|
||||||
|
group_identity_sha256 32 bytes
|
||||||
|
group_version u32, nonzero
|
||||||
|
optical_state_sha256 32 bytes
|
||||||
|
target_sha256 32 bytes
|
||||||
|
solver_executable_sha256 32 bytes
|
||||||
|
solver_configuration_sha256 32 bytes
|
||||||
|
initialization_evidence_sha256 32 bytes
|
||||||
|
validation_evidence_sha256 32 bytes
|
||||||
|
member_count u32, nonzero
|
||||||
|
|
||||||
|
for each member in strictly increasing selected_item_index order:
|
||||||
|
selected_item_index u32
|
||||||
|
image_id u64, nonzero
|
||||||
|
representation_sha256 32 bytes
|
||||||
|
width, height u32, u32, nonzero
|
||||||
|
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 pinhole parameters and four validation bits have exactly the v1 meaning.
|
||||||
|
V2 introduces no new solver threshold. Positive focal lengths and principal
|
||||||
|
points inside the declared dimensions are required. Every selected item index
|
||||||
|
from zero through `entry_count - 1` occurs exactly once across the artifact,
|
||||||
|
and every `image_id` occurs exactly once. Group order and member order are
|
||||||
|
validated, not silently normalized.
|
||||||
|
|
||||||
|
## Provenance and publication
|
||||||
|
|
||||||
|
Each complete serialized group record, including its exact membership and all
|
||||||
|
group-local evidence, is independently SHA-256 hashed. That group digest is the
|
||||||
|
`IMPORTED_TRUSTED` provenance fingerprint for each calibration in that group.
|
||||||
|
The independently supplied whole-artifact SHA-256 protects transport and binds
|
||||||
|
the combined publication without replacing group-local provenance.
|
||||||
|
|
||||||
|
Bootstrap validates the whole artifact and then verifies every entry against
|
||||||
|
the immutable selected execution's exact item index, `image_id`, and current
|
||||||
|
representation asset SHA-256 before its first write. It creates or reuses the
|
||||||
|
existing content-addressed Sparse calibrations per entry, creates or reuses one
|
||||||
|
scope over every selected image across all groups, and attaches only that
|
||||||
|
complete scope. A pre-publication error attaches no scope. A later failure may
|
||||||
|
retain immutable calibration evidence but cannot make the execution READY.
|
||||||
|
Exact retries reuse calibrations and the scope and converge on the same
|
||||||
|
attachment.
|
||||||
|
|
@ -24,7 +24,7 @@ SOURCE_COMMENT_AUDIT PASS
|
||||||
PRODUCT_DEFINITION PASS/FROZEN
|
PRODUCT_DEFINITION PASS/FROZEN
|
||||||
PROMPT_TREE CURRENT
|
PROMPT_TREE CURRENT
|
||||||
USER_FACING_UI_LANGUAGE_NORMALIZATION PASS
|
USER_FACING_UI_LANGUAGE_NORMALIZATION PASS
|
||||||
CURRENT_NEXT CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION
|
CURRENT_NEXT CALIBRATION_V2_WORKFLOW_READY
|
||||||
```
|
```
|
||||||
|
|
||||||
The current Project DB head is additive:
|
The current Project DB head is additive:
|
||||||
|
|
@ -534,8 +534,11 @@ v26 heterogeneous-optics foundation now durably records Capture geometric state
|
||||||
applicability while retaining that per-image scope model. Unknown state remains retained but
|
applicability while retaining that per-image scope model. Unknown state remains retained but
|
||||||
`CALIBRATION_REQUIRED`, and exact compatibility resolves none/one/many candidates to
|
`CALIBRATION_REQUIRED`, and exact compatibility resolves none/one/many candidates to
|
||||||
`CALIBRATION_REQUIRED`/resolved/`SELECTION_REQUIRED`. The next dependency is heterogeneous
|
`CALIBRATION_REQUIRED`/resolved/`SELECTION_REQUIRED`. The next dependency is heterogeneous
|
||||||
calibration publication. Device-specific autofocus envelopes remain blocked until physical evidence
|
calibration publication. `CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PASS/FROZEN`:
|
||||||
validates them.
|
additive L3DCALB2/Tooling/Bootstrap v2 now retains independent group-local provenance, validates
|
||||||
|
complete exact selected-image coverage, and attaches one existing per-image scope only after complete
|
||||||
|
publication. The next dependency is heterogeneous Workflow v2 truthful READY. Device-specific
|
||||||
|
autofocus envelopes remain blocked until physical evidence validates them.
|
||||||
|
|
||||||
Calibration Tooling v1 consumes an already acquired Science v1 evidence bundle, validates the bounded
|
Calibration Tooling v1 consumes an already acquired Science v1 evidence bundle, validates the bounded
|
||||||
contract and produces deterministic `L3DCALB1` v1.
|
contract and produces deterministic `L3DCALB1` v1.
|
||||||
|
|
|
||||||
64
include/lardon3d/calibration_bootstrap_v2.h
Normal file
64
include/lardon3d/calibration_bootstrap_v2.h
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
#ifndef LARDON3D_CALIBRATION_BOOTSTRAP_V2_H
|
||||||
|
#define LARDON3D_CALIBRATION_BOOTSTRAP_V2_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_V2_ARTIFACT_VERSION = 2,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_BYTES = 600000,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_GROUPS = 4096,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES = 4096,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK = 0,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_PROVENANCE_MISMATCH,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR,
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_OUT_OF_MEMORY,
|
||||||
|
} Lardon3DCalibrationBootstrapV2Result;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
unsigned char artifact_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE];
|
||||||
|
Lardon3DSparseCalibrationScope scope;
|
||||||
|
uint32_t calibration_count;
|
||||||
|
uint32_t group_count;
|
||||||
|
} Lardon3DCalibrationBootstrapV2Output;
|
||||||
|
|
||||||
|
/* Import one complete L3DCALB2 artifact for an immutable selected execution.
|
||||||
|
* All pointers are required and borrowed only for the call; artifact_size is
|
||||||
|
* bounded by V2_MAX_BYTES and must match expected_artifact_sha256 before any
|
||||||
|
* parsing or database access. The fixed-width little-endian format accepts
|
||||||
|
* only the existing eight-parameter PINHOLE model and complete, nonoverlapping
|
||||||
|
* coverage of selected item indexes, image IDs, and representation SHA-256s.
|
||||||
|
*
|
||||||
|
* Each serialized group is hashed independently and that digest becomes every
|
||||||
|
* member calibration's IMPORTED_TRUSTED provenance fingerprint. Thus target,
|
||||||
|
* optical-state, solver, initialization, validation, and exact membership
|
||||||
|
* evidence remain group-local even when one scope contains heterogeneous
|
||||||
|
* groups. Validation completes before publication. Existing immutable
|
||||||
|
* calibrations and one exact all-image scope are reused on retry; the scope is
|
||||||
|
* attached only after complete publication, so a failure cannot make the
|
||||||
|
* execution READY. No Capture identity, solver result beyond intrinsics, or
|
||||||
|
* compatibility relation is inferred by this C ABI. */
|
||||||
|
Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_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],
|
||||||
|
Lardon3DCalibrationBootstrapV2Output *output);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
103
include/lardon3d/calibration_tooling_v2.h
Normal file
103
include/lardon3d/calibration_tooling_v2.h
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
#ifndef LARDON3D_CALIBRATION_TOOLING_V2_H
|
||||||
|
#define LARDON3D_CALIBRATION_TOOLING_V2_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <lardon3d/calibration_bootstrap_v2.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum {
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_VERSION = 2,
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_VALIDATION_FLAGS = 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_OK = 0,
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_INVALID_ARGUMENT,
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED,
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_CAPACITY,
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_ENCODING_ERROR,
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_IMPORT_ERROR,
|
||||||
|
} Lardon3DCalibrationToolingV2Result;
|
||||||
|
|
||||||
|
/* One selected-image member of a caller-declared scientific group. The item
|
||||||
|
* index is the immutable selected-execution order, not an operational group
|
||||||
|
* index. Parameters use the exact v1/OpenCV pinhole meaning. All storage is
|
||||||
|
* caller-owned and borrowed only while the API call is active. */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t selected_item_index;
|
||||||
|
uint64_t image_id;
|
||||||
|
unsigned char representation_sha256[32];
|
||||||
|
uint32_t width;
|
||||||
|
uint32_t height;
|
||||||
|
double fx, fy, cx, cy, k1, k2, p1, p2;
|
||||||
|
uint32_t support_images;
|
||||||
|
uint32_t support_observations;
|
||||||
|
double reprojection_rmse_px;
|
||||||
|
double maximum_parameter_delta;
|
||||||
|
uint32_t validation_flags;
|
||||||
|
} Lardon3DCalibrationToolingV2Entry;
|
||||||
|
|
||||||
|
/* Group identity is the exact bounded state identifier plus its explicit
|
||||||
|
* version. The six nonzero digests independently bind optical applicability,
|
||||||
|
* physical target, solver executable/configuration, initialization, and
|
||||||
|
* validation evidence. Entries must be strictly increasing by selected item
|
||||||
|
* index; groups must be strictly increasing by (group_identity_sha256,
|
||||||
|
* group_version). These canonical orders make identical evidence byte-stable
|
||||||
|
* without hiding caller mistakes by silently reordering it. */
|
||||||
|
typedef struct {
|
||||||
|
unsigned char group_identity_sha256[32];
|
||||||
|
uint32_t group_version;
|
||||||
|
unsigned char optical_state_sha256[32];
|
||||||
|
unsigned char target_sha256[32];
|
||||||
|
unsigned char solver_executable_sha256[32];
|
||||||
|
unsigned char solver_configuration_sha256[32];
|
||||||
|
unsigned char initialization_evidence_sha256[32];
|
||||||
|
unsigned char validation_evidence_sha256[32];
|
||||||
|
const Lardon3DCalibrationToolingV2Entry *entries;
|
||||||
|
size_t entry_count;
|
||||||
|
} Lardon3DCalibrationToolingV2Group;
|
||||||
|
|
||||||
|
/* A bounded heterogeneous publication manifest. Every item index in
|
||||||
|
* [0, entry_count) must occur exactly once across all groups, and every image
|
||||||
|
* ID must be globally unique. Tooling validates only explicit evidence and
|
||||||
|
* model-domain safety; it does not run a solver, invent acceptance thresholds,
|
||||||
|
* or decide optical compatibility. */
|
||||||
|
typedef struct {
|
||||||
|
const Lardon3DCalibrationToolingV2Group *groups;
|
||||||
|
size_t group_count;
|
||||||
|
size_t entry_count;
|
||||||
|
} Lardon3DCalibrationToolingV2Evidence;
|
||||||
|
|
||||||
|
/* Validate borrowed evidence and its borrowed nested group/entry arrays for
|
||||||
|
* bounded counts, canonical ordering, and exact selected-index/image coverage.
|
||||||
|
* Validation does not mutate evidence, artifact bytes, or Project DB state.
|
||||||
|
* INVALID_ARGUMENT reports a missing top-level pointer/groups array or
|
||||||
|
* out-of-range top-level counts; EVIDENCE_REJECTED reports invalid nested or
|
||||||
|
* scientific evidence within an otherwise bounded top-level manifest. */
|
||||||
|
Lardon3DCalibrationToolingV2Result lardon3d_calibration_tooling_v2_validate(
|
||||||
|
const Lardon3DCalibrationToolingV2Evidence *evidence);
|
||||||
|
|
||||||
|
/* Encode deterministic L3DCALB2 into caller storage and return its SHA-256.
|
||||||
|
* `written` may be NULL; no partial artifact is reported as written. */
|
||||||
|
Lardon3DCalibrationToolingV2Result lardon3d_calibration_tooling_v2_produce(
|
||||||
|
const Lardon3DCalibrationToolingV2Evidence *evidence, unsigned char *artifact,
|
||||||
|
size_t artifact_capacity, size_t *written, unsigned char artifact_sha256[32]);
|
||||||
|
|
||||||
|
/* Validate, encode, then call the production v2 bootstrap importer. The DB is
|
||||||
|
* untouched when validation or encoding fails. */
|
||||||
|
Lardon3DCalibrationToolingV2Result lardon3d_calibration_tooling_v2_import(
|
||||||
|
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||||
|
const Lardon3DCalibrationToolingV2Evidence *evidence, unsigned char *artifact,
|
||||||
|
size_t artifact_capacity, size_t *written,
|
||||||
|
Lardon3DCalibrationBootstrapV2Output *output);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
17
meson.build
17
meson.build
|
|
@ -179,7 +179,9 @@ lardon3d_app = executable(
|
||||||
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
'src/project_db.c', 'src/project_db_sparse_sfm.c',
|
||||||
'src/optical_profiles.c',
|
'src/optical_profiles.c',
|
||||||
'src/calibration_bootstrap.c',
|
'src/calibration_bootstrap.c',
|
||||||
|
'src/calibration_bootstrap_v2.c',
|
||||||
'src/calibration_tooling.c',
|
'src/calibration_tooling.c',
|
||||||
|
'src/calibration_tooling_v2.c',
|
||||||
'src/calibration_workflow.cpp',
|
'src/calibration_workflow.cpp',
|
||||||
'src/calibration_workflow_materialize.cpp',
|
'src/calibration_workflow_materialize.cpp',
|
||||||
'src/calibration_workflow_bind.cpp',
|
'src/calibration_workflow_bind.cpp',
|
||||||
|
|
@ -899,6 +901,21 @@ calibration_tooling_test = executable(
|
||||||
|
|
||||||
test('calibration-tooling', calibration_tooling_test, timeout: 30)
|
test('calibration-tooling', calibration_tooling_test, timeout: 30)
|
||||||
|
|
||||||
|
calibration_publication_v2_test = executable(
|
||||||
|
'test-calibration-publication-v2',
|
||||||
|
sources: [
|
||||||
|
'tests/test_calibration_publication_v2.c',
|
||||||
|
'src/calibration_tooling_v2.c', 'src/calibration_bootstrap_v2.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, cc.find_library('m')],
|
||||||
|
)
|
||||||
|
|
||||||
|
test('calibration-publication-v2', calibration_publication_v2_test, timeout: 30)
|
||||||
|
|
||||||
calibration_workflow_test = executable(
|
calibration_workflow_test = executable(
|
||||||
'test-calibration-workflow',
|
'test-calibration-workflow',
|
||||||
sources: [
|
sources: [
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
CURRENT_PROJECT_DB_SCHEMA=v26
|
CURRENT_PROJECT_DB_SCHEMA=v26
|
||||||
PRODUCTION_TASK_KINDS=16
|
PRODUCTION_TASK_KINDS=16
|
||||||
USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS
|
USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS
|
||||||
CURRENT_IMPLEMENTATION_CURSOR=CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION
|
CURRENT_IMPLEMENTATION_CURSOR=CALIBRATION_V2_WORKFLOW_READY
|
||||||
```
|
```
|
||||||
|
|
||||||
## Authority
|
## Authority
|
||||||
|
|
@ -37,6 +37,7 @@ Calibration workflow selected-execution binding PASS/FROZEN
|
||||||
Calibration workflow Tooling/Bootstrap READY PASS/FROZEN
|
Calibration workflow Tooling/Bootstrap READY PASS/FROZEN
|
||||||
Calibration Science v2 heterogeneous optics PASS/FROZEN
|
Calibration Science v2 heterogeneous optics PASS/FROZEN
|
||||||
Calibration v2 optical-state foundation PASS/FROZEN
|
Calibration v2 optical-state foundation PASS/FROZEN
|
||||||
|
Calibration v2 heterogeneous publication PASS/FROZEN
|
||||||
Adaptive capture settings semantics PLANNED
|
Adaptive capture settings semantics PLANNED
|
||||||
Autofocus v2 foundation PLANNED
|
Autofocus v2 foundation PLANNED
|
||||||
Calibration Tooling planarity alignment PASS/FROZEN
|
Calibration Tooling planarity alignment PASS/FROZEN
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,11 @@ CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS/FROZEN
|
||||||
CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY_V1=PASS/FROZEN
|
CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY_V1=PASS/FROZEN
|
||||||
CALIBRATION_SCIENCE_V2=PASS/FROZEN
|
CALIBRATION_SCIENCE_V2=PASS/FROZEN
|
||||||
CALIBRATION_V2_HETEROGENEOUS_OPTICS=PASS/FROZEN
|
CALIBRATION_V2_HETEROGENEOUS_OPTICS=PASS/FROZEN
|
||||||
CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PLANNED
|
CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PASS/FROZEN
|
||||||
|
CALIBRATION_V2_WORKFLOW_READY=PLANNED
|
||||||
ADAPTIVE_CAPTURE_SETTINGS_CONTRACT=PLANNED
|
ADAPTIVE_CAPTURE_SETTINGS_CONTRACT=PLANNED
|
||||||
AUTOFOCUS_V2_FOUNDATION=PLANNED
|
AUTOFOCUS_V2_FOUNDATION=PLANNED
|
||||||
CURRENT_CALIBRATION_NEXT=CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION
|
CURRENT_CALIBRATION_NEXT=CALIBRATION_V2_WORKFLOW_READY
|
||||||
```
|
```
|
||||||
|
|
||||||
## Authority
|
## Authority
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
IMPLEMENTATION_ORDER=DEPENDENCY_DRIVEN
|
IMPLEMENTATION_ORDER=DEPENDENCY_DRIVEN
|
||||||
IMPLEMENTATION_AUTHORIZATION=NO
|
IMPLEMENTATION_AUTHORIZATION=NO
|
||||||
STEP_0_USER_FACING_LANGUAGE_NORMALIZATION=PASS
|
STEP_0_USER_FACING_LANGUAGE_NORMALIZATION=PASS
|
||||||
CURRENT_NEXT=CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION
|
CURRENT_NEXT=CALIBRATION_V2_WORKFLOW_READY
|
||||||
```
|
```
|
||||||
|
|
||||||
## Authority
|
## Authority
|
||||||
|
|
@ -23,23 +23,24 @@ Default dependency order:
|
||||||
1. final usable Calibration Science/Tooling/Bootstrap/Workflow v1 compatibility path — PASS/FROZEN;
|
1. final usable Calibration Science/Tooling/Bootstrap/Workflow v1 compatibility path — PASS/FROZEN;
|
||||||
2. Calibration Science v2 design for heterogeneous cameras/lenses/focals, adaptive capture settings and autofocus — PASS/FROZEN;
|
2. Calibration Science v2 design for heterogeneous cameras/lenses/focals, adaptive capture settings and autofocus — PASS/FROZEN;
|
||||||
3. Calibration v2 heterogeneous-optics persistence foundation — PASS/FROZEN;
|
3. Calibration v2 heterogeneous-optics persistence foundation — PASS/FROZEN;
|
||||||
4. Heterogeneous calibration publication / Tooling / Bootstrap evolution — CURRENT;
|
4. Heterogeneous calibration publication / Tooling / Bootstrap evolution — PASS/FROZEN;
|
||||||
5. physical autofocus/optical applicability validation and dedicated calibrated real campaign;
|
5. Heterogeneous Workflow v2 truthful READY proof — CURRENT;
|
||||||
6. real Sparse SfM proof;
|
6. physical autofocus/optical applicability validation and dedicated calibrated real campaign;
|
||||||
7. durable Dense/OpenMVS orchestration;
|
7. real Sparse SfM proof;
|
||||||
8. mesh / refinement / texturing / export;
|
8. durable Dense/OpenMVS orchestration;
|
||||||
9. viewer foundation;
|
9. mesh / refinement / texturing / export;
|
||||||
10. offline Coverage Analysis scientific contract and implementation;
|
10. viewer foundation;
|
||||||
11. multi-campaign registration / fusion;
|
11. offline Coverage Analysis scientific contract and implementation;
|
||||||
12. generic live acquisition adapter foundation;
|
12. multi-campaign registration / fusion;
|
||||||
13. A6000 HDMI integration;
|
13. generic live acquisition adapter foundation;
|
||||||
14. S21 integration;
|
14. A6000 HDMI integration;
|
||||||
15. live camera localization;
|
15. S21 integration;
|
||||||
16. live coverage overlay;
|
16. live camera localization;
|
||||||
17. actionable Capture Guidance;
|
17. live coverage overlay;
|
||||||
18. video ingestion / deterministic keyframes;
|
18. actionable Capture Guidance;
|
||||||
19. final integration, UX, restart and performance proof;
|
19. video ingestion / deterministic keyframes;
|
||||||
20. Product Definition v1 Definition-of-Done closure.
|
20. final integration, UX, restart and performance proof;
|
||||||
|
21. Product Definition v1 Definition-of-Done closure.
|
||||||
|
|
||||||
## Adjustment rule
|
## Adjustment rule
|
||||||
|
|
||||||
|
|
|
||||||
312
src/calibration_bootstrap_v2.c
Normal file
312
src/calibration_bootstrap_v2.c
Normal file
|
|
@ -0,0 +1,312 @@
|
||||||
|
#include <lardon3d/calibration_bootstrap_v2.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
BOOTSTRAP_V2_HEADER_SIZE = 28,
|
||||||
|
BOOTSTRAP_V2_GROUP_SIZE = 232,
|
||||||
|
BOOTSTRAP_V2_ENTRY_SIZE = 144,
|
||||||
|
BOOTSTRAP_V2_VALIDATION_FLAGS = 15,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const unsigned char bootstrap_v2_magic[8] = {'L', '3', 'D', 'C', 'A', 'L', 'B', '2'};
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const unsigned char *bytes;
|
||||||
|
size_t remaining;
|
||||||
|
} BootstrapV2Reader;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint32_t selected_item_index;
|
||||||
|
unsigned char representation_sha256[32];
|
||||||
|
Lardon3DSparseCalibration calibration;
|
||||||
|
Lardon3DSparseCalibrationMember member;
|
||||||
|
} BootstrapV2Entry;
|
||||||
|
|
||||||
|
static bool take(BootstrapV2Reader *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(BootstrapV2Reader *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(BootstrapV2Reader *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(BootstrapV2Reader *reader, double *output) {
|
||||||
|
uint64_t bits = 0;
|
||||||
|
if (!read_u64(reader, &bits)) return false;
|
||||||
|
memcpy(output, &bits, sizeof(bits));
|
||||||
|
if (!isfinite(*output)) return false;
|
||||||
|
if (*output == 0.0) *output = 0.0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool nonzero_digest(const unsigned char value[32]) {
|
||||||
|
unsigned char any = 0;
|
||||||
|
for (size_t index = 0; index < 32; ++index) any |= value[index];
|
||||||
|
return any != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Lardon3DCalibrationBootstrapV2Result db_result(Lardon3DProjectDbResult result) {
|
||||||
|
if (result == LARDON3D_PROJECT_DB_CONSTRAINT || result == LARDON3D_PROJECT_DB_NOT_FOUND)
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
|
||||||
|
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||||
|
const unsigned char *artifact, size_t artifact_size,
|
||||||
|
const unsigned char expected_artifact_sha256[32],
|
||||||
|
Lardon3DCalibrationBootstrapV2Output *output) {
|
||||||
|
if (!database || execution_id == 0 || !artifact || !expected_artifact_sha256 || !output ||
|
||||||
|
artifact_size < BOOTSTRAP_V2_HEADER_SIZE ||
|
||||||
|
artifact_size > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_BYTES)
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT;
|
||||||
|
memset(output, 0, sizeof(*output));
|
||||||
|
|
||||||
|
unsigned char artifact_sha256[32];
|
||||||
|
if (!sha256(artifact, artifact_size, artifact_sha256))
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
|
||||||
|
if (memcmp(artifact_sha256, expected_artifact_sha256, 32) != 0)
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_PROVENANCE_MISMATCH;
|
||||||
|
|
||||||
|
BootstrapV2Reader reader = {artifact, artifact_size};
|
||||||
|
unsigned char magic[8];
|
||||||
|
uint32_t version, model_kind, model_version, group_count, entry_count;
|
||||||
|
if (!take(&reader, magic, sizeof(magic)) || !read_u32(&reader, &version) ||
|
||||||
|
!read_u32(&reader, &model_kind) || !read_u32(&reader, &model_version) ||
|
||||||
|
!read_u32(&reader, &group_count) || !read_u32(&reader, &entry_count) ||
|
||||||
|
memcmp(magic, bootstrap_v2_magic, sizeof(magic)) != 0 ||
|
||||||
|
version != LARDON3D_CALIBRATION_BOOTSTRAP_V2_ARTIFACT_VERSION ||
|
||||||
|
model_kind != LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE ||
|
||||||
|
model_version != LARDON3D_SPARSE_SFM_CALIBRATION_VERSION || group_count == 0 ||
|
||||||
|
group_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_GROUPS || entry_count == 0 ||
|
||||||
|
entry_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES)
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
size_t expected_size = BOOTSTRAP_V2_HEADER_SIZE + (size_t)group_count * BOOTSTRAP_V2_GROUP_SIZE;
|
||||||
|
if (entry_count > (SIZE_MAX - expected_size) / BOOTSTRAP_V2_ENTRY_SIZE)
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
expected_size += (size_t)entry_count * BOOTSTRAP_V2_ENTRY_SIZE;
|
||||||
|
if (artifact_size != expected_size)
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
|
||||||
|
BootstrapV2Entry *entries = calloc(entry_count, sizeof(*entries));
|
||||||
|
bool *covered = calloc(entry_count, sizeof(*covered));
|
||||||
|
if (!entries || !covered) {
|
||||||
|
free(entries);
|
||||||
|
free(covered);
|
||||||
|
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DCalibrationBootstrapV2Result result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK;
|
||||||
|
size_t parsed_entries = 0;
|
||||||
|
unsigned char previous_group_identity[32] = {0};
|
||||||
|
uint32_t previous_group_version = 0;
|
||||||
|
for (uint32_t group_index = 0;
|
||||||
|
group_index < group_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK;
|
||||||
|
++group_index) {
|
||||||
|
const unsigned char *group_start = reader.bytes;
|
||||||
|
unsigned char group_identity[32], evidence[6][32];
|
||||||
|
uint32_t group_version, member_count;
|
||||||
|
if (!take(&reader, group_identity, 32) || !read_u32(&reader, &group_version)) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < 6; ++index)
|
||||||
|
if (!take(&reader, evidence[index], 32)) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (result != LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK ||
|
||||||
|
!read_u32(&reader, &member_count)) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int group_order = group_index == 0 ? -1 :
|
||||||
|
memcmp(previous_group_identity, group_identity, 32);
|
||||||
|
if (!nonzero_digest(group_identity) || group_version == 0 || member_count == 0 ||
|
||||||
|
member_count > entry_count - parsed_entries || group_order > 0 ||
|
||||||
|
(group_order == 0 && previous_group_version >= group_version)) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (size_t index = 0; index < 6; ++index)
|
||||||
|
if (!nonzero_digest(evidence[index])) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
memcpy(previous_group_identity, group_identity, 32);
|
||||||
|
previous_group_version = group_version;
|
||||||
|
size_t group_first_entry = parsed_entries;
|
||||||
|
uint32_t previous_item_index = 0;
|
||||||
|
for (uint32_t member_index = 0;
|
||||||
|
member_index < member_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK;
|
||||||
|
++member_index) {
|
||||||
|
BootstrapV2Entry *entry = &entries[parsed_entries];
|
||||||
|
uint32_t width, height, support_images, support_observations, validation_flags;
|
||||||
|
double reprojection_rmse_px, maximum_parameter_delta;
|
||||||
|
if (!read_u32(&reader, &entry->selected_item_index) ||
|
||||||
|
!read_u64(&reader, &entry->member.image_id) ||
|
||||||
|
!take(&reader, entry->representation_sha256, 32) || !read_u32(&reader, &width) ||
|
||||||
|
!read_u32(&reader, &height) || !read_f64(&reader, &entry->calibration.fx) ||
|
||||||
|
!read_f64(&reader, &entry->calibration.fy) ||
|
||||||
|
!read_f64(&reader, &entry->calibration.cx) ||
|
||||||
|
!read_f64(&reader, &entry->calibration.cy) ||
|
||||||
|
!read_f64(&reader, &entry->calibration.k1) ||
|
||||||
|
!read_f64(&reader, &entry->calibration.k2) ||
|
||||||
|
!read_f64(&reader, &entry->calibration.p1) ||
|
||||||
|
!read_f64(&reader, &entry->calibration.p2) ||
|
||||||
|
!read_u32(&reader, &support_images) ||
|
||||||
|
!read_u32(&reader, &support_observations) ||
|
||||||
|
!read_f64(&reader, &reprojection_rmse_px) ||
|
||||||
|
!read_f64(&reader, &maximum_parameter_delta) ||
|
||||||
|
!read_u32(&reader, &validation_flags) ||
|
||||||
|
entry->selected_item_index >= entry_count || covered[entry->selected_item_index] ||
|
||||||
|
(member_index > 0 && previous_item_index >= entry->selected_item_index) ||
|
||||||
|
entry->member.image_id == 0 || !nonzero_digest(entry->representation_sha256) ||
|
||||||
|
width == 0 || height == 0 || entry->calibration.fx <= 0.0 ||
|
||||||
|
entry->calibration.fy <= 0.0 || entry->calibration.cx < 0.0 ||
|
||||||
|
entry->calibration.cy < 0.0 || entry->calibration.cx >= (double)width ||
|
||||||
|
entry->calibration.cy >= (double)height || support_images == 0 ||
|
||||||
|
support_observations == 0 || reprojection_rmse_px < 0.0 ||
|
||||||
|
maximum_parameter_delta < 0.0 || validation_flags != BOOTSTRAP_V2_VALIDATION_FLAGS) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (size_t prior = 0; prior < parsed_entries; ++prior)
|
||||||
|
if (entries[prior].member.image_id == entry->member.image_id) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
covered[entry->selected_item_index] = true;
|
||||||
|
previous_item_index = entry->selected_item_index;
|
||||||
|
entry->calibration.model_kind = model_kind;
|
||||||
|
entry->calibration.model_version = model_version;
|
||||||
|
entry->calibration.width = width;
|
||||||
|
entry->calibration.height = height;
|
||||||
|
entry->calibration.provenance_kind =
|
||||||
|
LARDON3D_SPARSE_SFM_PROVENANCE_IMPORTED_TRUSTED;
|
||||||
|
++parsed_entries;
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
|
||||||
|
unsigned char group_fingerprint[32];
|
||||||
|
size_t group_size = (size_t)(reader.bytes - group_start);
|
||||||
|
if (!sha256(group_start, group_size, group_fingerprint)) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* Group-local hashing preserves independently acquired provenance: an
|
||||||
|
* unrelated group's addition cannot redefine these calibrations. */
|
||||||
|
for (size_t index = group_first_entry; index < parsed_entries; ++index)
|
||||||
|
memcpy(entries[index].calibration.provenance_fingerprint, group_fingerprint, 32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK &&
|
||||||
|
(parsed_entries != entry_count || reader.remaining != 0))
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
for (uint32_t index = 0;
|
||||||
|
index < entry_count && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK; ++index)
|
||||||
|
if (!covered[index]) result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||||
|
|
||||||
|
Lardon3DProjectDbSelectedExecution execution;
|
||||||
|
Lardon3DProjectDbResult db_status = LARDON3D_PROJECT_DB_OK;
|
||||||
|
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
|
||||||
|
db_status = lardon3d_project_db_load_selected_execution(database, execution_id, &execution);
|
||||||
|
if (db_status != LARDON3D_PROJECT_DB_OK)
|
||||||
|
result = db_result(db_status);
|
||||||
|
else if (execution.item_count != entry_count ||
|
||||||
|
(execution.stage != LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
|
||||||
|
execution.stage != LARDON3D_SELECTED_EXECUTION_READY))
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
|
||||||
|
}
|
||||||
|
for (size_t index = 0;
|
||||||
|
index < parsed_entries && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK; ++index) {
|
||||||
|
BootstrapV2Entry *entry = &entries[index];
|
||||||
|
Lardon3DProjectDbSelectedExecutionItem selected;
|
||||||
|
Lardon3DProjectDbImage image;
|
||||||
|
Lardon3DProjectDbImageAsset asset;
|
||||||
|
db_status = lardon3d_project_db_load_selected_execution_item(
|
||||||
|
database, execution_id, entry->selected_item_index, &selected);
|
||||||
|
if (db_status != LARDON3D_PROJECT_DB_OK || !selected.has_image ||
|
||||||
|
selected.image_id != entry->member.image_id) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
db_status = lardon3d_project_db_load_image(database, entry->member.image_id, &image, &asset);
|
||||||
|
if (db_status != LARDON3D_PROJECT_DB_OK ||
|
||||||
|
memcmp(asset.sha256, entry->representation_sha256, 32) != 0) {
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* All bytes and immutable selected bindings are verified before this first
|
||||||
|
* write. Publication uses the existing short, content-addressed DB APIs; a
|
||||||
|
* crash may retain reusable calibrations but READY remains guarded by the
|
||||||
|
* single complete-scope attachment below. */
|
||||||
|
for (size_t index = 0;
|
||||||
|
index < parsed_entries && result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK; ++index) {
|
||||||
|
Lardon3DSparseCalibration stored;
|
||||||
|
db_status = lardon3d_sparse_calibration_create(database, &entries[index].calibration,
|
||||||
|
&stored);
|
||||||
|
if (db_status != LARDON3D_PROJECT_DB_OK) {
|
||||||
|
result = db_result(db_status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
entries[index].member.calibration_id = stored.calibration_id;
|
||||||
|
memcpy(entries[index].member.calibration_hash, stored.scientific_hash, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DSparseCalibrationScope scope;
|
||||||
|
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
|
||||||
|
Lardon3DSparseCalibrationMember *members = calloc(entry_count, sizeof(*members));
|
||||||
|
if (!members)
|
||||||
|
result = LARDON3D_CALIBRATION_BOOTSTRAP_V2_OUT_OF_MEMORY;
|
||||||
|
else {
|
||||||
|
for (size_t index = 0; index < entry_count; ++index) members[index] = entries[index].member;
|
||||||
|
db_status = lardon3d_sparse_calibration_scope_create(database, members, entry_count, &scope);
|
||||||
|
free(members);
|
||||||
|
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
|
||||||
|
db_status = lardon3d_project_db_assign_selected_calibration_scope(database, execution_id,
|
||||||
|
scope.scope_id);
|
||||||
|
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
|
||||||
|
}
|
||||||
|
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
|
||||||
|
memcpy(output->artifact_sha256, artifact_sha256, 32);
|
||||||
|
output->scope = scope;
|
||||||
|
output->calibration_count = entry_count;
|
||||||
|
output->group_count = group_count;
|
||||||
|
}
|
||||||
|
free(entries);
|
||||||
|
free(covered);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
198
src/calibration_tooling_v2.c
Normal file
198
src/calibration_tooling_v2.c
Normal file
|
|
@ -0,0 +1,198 @@
|
||||||
|
#include <lardon3d/calibration_tooling_v2.h>
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
TOOLING_V2_HEADER_SIZE = 28,
|
||||||
|
TOOLING_V2_GROUP_SIZE = 232,
|
||||||
|
TOOLING_V2_ENTRY_SIZE = 144,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool nonzero_digest(const unsigned char value[32]) {
|
||||||
|
unsigned char any = 0;
|
||||||
|
for (size_t index = 0; index < 32; ++index) any |= value[index];
|
||||||
|
return any != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u32(unsigned char *output, uint32_t value) {
|
||||||
|
for (size_t index = 0; index < 4; ++index)
|
||||||
|
output[index] = (unsigned char)(value >> (8u * index));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_u64(unsigned char *output, uint64_t value) {
|
||||||
|
for (size_t index = 0; index < 8; ++index)
|
||||||
|
output[index] = (unsigned char)(value >> (8u * index));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void put_f64(unsigned char *output, double value) {
|
||||||
|
uint64_t bits = 0;
|
||||||
|
/* The format has one representation of zero so equal scientific values do
|
||||||
|
* not acquire different artifact identities through a negative sign bit. */
|
||||||
|
if (value == 0.0) value = 0.0;
|
||||||
|
memcpy(&bits, &value, sizeof(bits));
|
||||||
|
put_u64(output, bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 bool entry_valid(const Lardon3DCalibrationToolingV2Entry *entry) {
|
||||||
|
const double parameters[] = {entry->fx, entry->fy, entry->cx, entry->cy,
|
||||||
|
entry->k1, entry->k2, entry->p1, entry->p2};
|
||||||
|
if (entry->image_id == 0 || !nonzero_digest(entry->representation_sha256) ||
|
||||||
|
entry->width == 0 || entry->height == 0 || entry->support_images == 0 ||
|
||||||
|
entry->support_observations == 0 ||
|
||||||
|
entry->validation_flags != LARDON3D_CALIBRATION_TOOLING_V2_VALIDATION_FLAGS)
|
||||||
|
return false;
|
||||||
|
for (size_t index = 0; index < sizeof(parameters) / sizeof(parameters[0]); ++index)
|
||||||
|
if (!isfinite(parameters[index])) return false;
|
||||||
|
return entry->fx > 0.0 && entry->fy > 0.0 && entry->cx >= 0.0 && entry->cy >= 0.0 &&
|
||||||
|
entry->cx < (double)entry->width && entry->cy < (double)entry->height &&
|
||||||
|
isfinite(entry->reprojection_rmse_px) && entry->reprojection_rmse_px >= 0.0 &&
|
||||||
|
isfinite(entry->maximum_parameter_delta) && entry->maximum_parameter_delta >= 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DCalibrationToolingV2Result lardon3d_calibration_tooling_v2_validate(
|
||||||
|
const Lardon3DCalibrationToolingV2Evidence *evidence) {
|
||||||
|
if (!evidence || !evidence->groups || evidence->group_count == 0 ||
|
||||||
|
evidence->group_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_GROUPS ||
|
||||||
|
evidence->entry_count == 0 ||
|
||||||
|
evidence->entry_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_INVALID_ARGUMENT;
|
||||||
|
|
||||||
|
bool covered[LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES] = {false};
|
||||||
|
size_t total_entries = 0;
|
||||||
|
for (size_t group_index = 0; group_index < evidence->group_count; ++group_index) {
|
||||||
|
const Lardon3DCalibrationToolingV2Group *group = &evidence->groups[group_index];
|
||||||
|
if (!group->entries || group->entry_count == 0 || group->group_version == 0 ||
|
||||||
|
!nonzero_digest(group->group_identity_sha256) ||
|
||||||
|
!nonzero_digest(group->optical_state_sha256) || !nonzero_digest(group->target_sha256) ||
|
||||||
|
!nonzero_digest(group->solver_executable_sha256) ||
|
||||||
|
!nonzero_digest(group->solver_configuration_sha256) ||
|
||||||
|
!nonzero_digest(group->initialization_evidence_sha256) ||
|
||||||
|
!nonzero_digest(group->validation_evidence_sha256))
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED;
|
||||||
|
if (group_index > 0) {
|
||||||
|
const Lardon3DCalibrationToolingV2Group *previous = &evidence->groups[group_index - 1];
|
||||||
|
int order = memcmp(previous->group_identity_sha256, group->group_identity_sha256, 32);
|
||||||
|
if (order > 0 || (order == 0 && previous->group_version >= group->group_version))
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED;
|
||||||
|
}
|
||||||
|
if (group->entry_count > evidence->entry_count - total_entries)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED;
|
||||||
|
total_entries += group->entry_count;
|
||||||
|
for (size_t entry_index = 0; entry_index < group->entry_count; ++entry_index) {
|
||||||
|
const Lardon3DCalibrationToolingV2Entry *entry = &group->entries[entry_index];
|
||||||
|
if (!entry_valid(entry) || entry->selected_item_index >= evidence->entry_count ||
|
||||||
|
covered[entry->selected_item_index] ||
|
||||||
|
(entry_index > 0 &&
|
||||||
|
group->entries[entry_index - 1].selected_item_index >= entry->selected_item_index))
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED;
|
||||||
|
covered[entry->selected_item_index] = true;
|
||||||
|
/* Scientific images cannot belong to two groups under different item
|
||||||
|
* indexes; item coverage alone would not detect that identity conflict. */
|
||||||
|
for (size_t prior_group = 0; prior_group <= group_index; ++prior_group) {
|
||||||
|
const Lardon3DCalibrationToolingV2Group *candidate_group =
|
||||||
|
&evidence->groups[prior_group];
|
||||||
|
size_t limit = prior_group == group_index ? entry_index : candidate_group->entry_count;
|
||||||
|
for (size_t prior_entry = 0; prior_entry < limit; ++prior_entry)
|
||||||
|
if (candidate_group->entries[prior_entry].image_id == entry->image_id)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (total_entries != evidence->entry_count)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED;
|
||||||
|
for (size_t index = 0; index < evidence->entry_count; ++index)
|
||||||
|
if (!covered[index]) return LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED;
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DCalibrationToolingV2Result lardon3d_calibration_tooling_v2_produce(
|
||||||
|
const Lardon3DCalibrationToolingV2Evidence *evidence, unsigned char *artifact,
|
||||||
|
size_t artifact_capacity, size_t *written, unsigned char artifact_sha256[32]) {
|
||||||
|
if (written) *written = 0;
|
||||||
|
if (!artifact || !artifact_sha256) return LARDON3D_CALIBRATION_TOOLING_V2_INVALID_ARGUMENT;
|
||||||
|
Lardon3DCalibrationToolingV2Result result =
|
||||||
|
lardon3d_calibration_tooling_v2_validate(evidence);
|
||||||
|
if (result != LARDON3D_CALIBRATION_TOOLING_V2_OK) return result;
|
||||||
|
if (evidence->group_count > (SIZE_MAX - TOOLING_V2_HEADER_SIZE) / TOOLING_V2_GROUP_SIZE)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_CAPACITY;
|
||||||
|
size_t size = TOOLING_V2_HEADER_SIZE + evidence->group_count * TOOLING_V2_GROUP_SIZE;
|
||||||
|
if (evidence->entry_count > (SIZE_MAX - size) / TOOLING_V2_ENTRY_SIZE)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_CAPACITY;
|
||||||
|
size += evidence->entry_count * TOOLING_V2_ENTRY_SIZE;
|
||||||
|
if (size > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_BYTES || artifact_capacity < size)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_CAPACITY;
|
||||||
|
|
||||||
|
size_t at = 0;
|
||||||
|
memcpy(artifact + at, "L3DCALB2", 8);
|
||||||
|
at += 8;
|
||||||
|
#define PUT32(value) do { put_u32(artifact + at, (uint32_t)(value)); at += 4; } while (0)
|
||||||
|
PUT32(LARDON3D_CALIBRATION_TOOLING_V2_VERSION);
|
||||||
|
PUT32(LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE);
|
||||||
|
PUT32(LARDON3D_SPARSE_SFM_CALIBRATION_VERSION);
|
||||||
|
PUT32(evidence->group_count);
|
||||||
|
PUT32(evidence->entry_count);
|
||||||
|
for (size_t group_index = 0; group_index < evidence->group_count; ++group_index) {
|
||||||
|
const Lardon3DCalibrationToolingV2Group *group = &evidence->groups[group_index];
|
||||||
|
const unsigned char *digests[] = {
|
||||||
|
group->group_identity_sha256, group->optical_state_sha256, group->target_sha256,
|
||||||
|
group->solver_executable_sha256, group->solver_configuration_sha256,
|
||||||
|
group->initialization_evidence_sha256, group->validation_evidence_sha256};
|
||||||
|
memcpy(artifact + at, digests[0], 32); at += 32;
|
||||||
|
PUT32(group->group_version);
|
||||||
|
for (size_t digest_index = 1; digest_index < 7; ++digest_index) {
|
||||||
|
memcpy(artifact + at, digests[digest_index], 32);
|
||||||
|
at += 32;
|
||||||
|
}
|
||||||
|
PUT32(group->entry_count);
|
||||||
|
for (size_t entry_index = 0; entry_index < group->entry_count; ++entry_index) {
|
||||||
|
const Lardon3DCalibrationToolingV2Entry *entry = &group->entries[entry_index];
|
||||||
|
PUT32(entry->selected_item_index);
|
||||||
|
put_u64(artifact + at, entry->image_id); at += 8;
|
||||||
|
memcpy(artifact + at, entry->representation_sha256, 32); at += 32;
|
||||||
|
PUT32(entry->width); PUT32(entry->height);
|
||||||
|
const double parameters[] = {entry->fx, entry->fy, entry->cx, entry->cy,
|
||||||
|
entry->k1, entry->k2, entry->p1, entry->p2};
|
||||||
|
for (size_t index = 0; index < 8; ++index) {
|
||||||
|
put_f64(artifact + at, parameters[index]); at += 8;
|
||||||
|
}
|
||||||
|
PUT32(entry->support_images); PUT32(entry->support_observations);
|
||||||
|
put_f64(artifact + at, entry->reprojection_rmse_px); at += 8;
|
||||||
|
put_f64(artifact + at, entry->maximum_parameter_delta); at += 8;
|
||||||
|
PUT32(entry->validation_flags);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef PUT32
|
||||||
|
if (at != size || !sha256(artifact, size, artifact_sha256))
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_ENCODING_ERROR;
|
||||||
|
if (written) *written = size;
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
Lardon3DCalibrationToolingV2Result lardon3d_calibration_tooling_v2_import(
|
||||||
|
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||||
|
const Lardon3DCalibrationToolingV2Evidence *evidence, unsigned char *artifact,
|
||||||
|
size_t artifact_capacity, size_t *written,
|
||||||
|
Lardon3DCalibrationBootstrapV2Output *output) {
|
||||||
|
unsigned char artifact_sha256[32];
|
||||||
|
size_t local_written = 0;
|
||||||
|
size_t *encoded_size = written ? written : &local_written;
|
||||||
|
Lardon3DCalibrationToolingV2Result result = lardon3d_calibration_tooling_v2_produce(
|
||||||
|
evidence, artifact, artifact_capacity, encoded_size, artifact_sha256);
|
||||||
|
if (result != LARDON3D_CALIBRATION_TOOLING_V2_OK) return result;
|
||||||
|
if (!database || execution_id == 0 || !output)
|
||||||
|
return LARDON3D_CALIBRATION_TOOLING_V2_INVALID_ARGUMENT;
|
||||||
|
return lardon3d_calibration_bootstrap_v2_import(
|
||||||
|
database, execution_id, artifact, *encoded_size, artifact_sha256, output) ==
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK
|
||||||
|
? LARDON3D_CALIBRATION_TOOLING_V2_OK
|
||||||
|
: LARDON3D_CALIBRATION_TOOLING_V2_IMPORT_ERROR;
|
||||||
|
}
|
||||||
348
tests/test_calibration_publication_v2.c
Normal file
348
tests/test_calibration_publication_v2.c
Normal file
|
|
@ -0,0 +1,348 @@
|
||||||
|
#include <math.h>
|
||||||
|
#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_tooling_v2.h>
|
||||||
|
|
||||||
|
#define CHECK(condition) do { if (!(condition)) { \
|
||||||
|
fprintf(stderr, "calibration publication v2 failure at line %d: %s\n", \
|
||||||
|
__LINE__, #condition); \
|
||||||
|
return false; \
|
||||||
|
} } while (0)
|
||||||
|
|
||||||
|
enum {
|
||||||
|
ARTIFACT_SIZE = 780,
|
||||||
|
ARTIFACT_HEADER_SIZE = 28,
|
||||||
|
GROUP_RECORD_SIZE = 376,
|
||||||
|
GROUP_MEMBER_OFFSET = 232,
|
||||||
|
MEMBER_SELECTED_INDEX_OFFSET = 0,
|
||||||
|
MEMBER_IMAGE_ID_OFFSET = 4,
|
||||||
|
MEMBER_FX_OFFSET = 52,
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool sql(const char *path, const char *text) {
|
||||||
|
sqlite3 *database = NULL;
|
||||||
|
if (sqlite3_open(path, &database) != SQLITE_OK) return false;
|
||||||
|
int result = sqlite3_exec(database, text, NULL, NULL, NULL);
|
||||||
|
return sqlite3_close(database) == SQLITE_OK && result == SQLITE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool scalar(const char *path, const char *text, int *value) {
|
||||||
|
sqlite3 *database = NULL;
|
||||||
|
sqlite3_stmt *statement = NULL;
|
||||||
|
if (sqlite3_open(path, &database) != SQLITE_OK ||
|
||||||
|
sqlite3_prepare_v2(database, text, -1, &statement, NULL) != SQLITE_OK) {
|
||||||
|
if (statement) sqlite3_finalize(statement);
|
||||||
|
if (database) sqlite3_close(database);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool ok = sqlite3_step(statement) == SQLITE_ROW;
|
||||||
|
if (ok) *value = sqlite3_column_int(statement, 0);
|
||||||
|
return sqlite3_finalize(statement) == SQLITE_OK &&
|
||||||
|
sqlite3_close(database) == SQLITE_OK && ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 fill_digest(unsigned char output[32], unsigned char seed) {
|
||||||
|
memset(output, seed, 32);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_u32_le(unsigned char output[4], uint32_t value) {
|
||||||
|
for (size_t index = 0; index < 4; ++index)
|
||||||
|
output[index] = (unsigned char)(value >> (8u * index));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_u64_le(unsigned char output[8], uint64_t value) {
|
||||||
|
for (size_t index = 0; index < 8; ++index)
|
||||||
|
output[index] = (unsigned char)(value >> (8u * index));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Synthetic evidence deliberately represents two independent optical groups;
|
||||||
|
* it tests publication mechanics and does not claim a physical calibration. */
|
||||||
|
static void evidence_fixture(Lardon3DCalibrationToolingV2Evidence *evidence,
|
||||||
|
Lardon3DCalibrationToolingV2Group groups[2],
|
||||||
|
Lardon3DCalibrationToolingV2Entry entries[2]) {
|
||||||
|
memset(evidence, 0, sizeof(*evidence));
|
||||||
|
memset(groups, 0, 2 * sizeof(*groups));
|
||||||
|
memset(entries, 0, 2 * sizeof(*entries));
|
||||||
|
for (size_t index = 0; index < 2; ++index) {
|
||||||
|
Lardon3DCalibrationToolingV2Group *group = &groups[index];
|
||||||
|
Lardon3DCalibrationToolingV2Entry *entry = &entries[index];
|
||||||
|
fill_digest(group->group_identity_sha256, (unsigned char)(1 + index));
|
||||||
|
group->group_version = 1;
|
||||||
|
fill_digest(group->optical_state_sha256, (unsigned char)(10 + index));
|
||||||
|
fill_digest(group->target_sha256, (unsigned char)(20 + index));
|
||||||
|
fill_digest(group->solver_executable_sha256, (unsigned char)(30 + index));
|
||||||
|
fill_digest(group->solver_configuration_sha256, (unsigned char)(40 + index));
|
||||||
|
fill_digest(group->initialization_evidence_sha256, (unsigned char)(50 + index));
|
||||||
|
fill_digest(group->validation_evidence_sha256, (unsigned char)(60 + index));
|
||||||
|
group->entries = entry;
|
||||||
|
group->entry_count = 1;
|
||||||
|
|
||||||
|
entry->selected_item_index = (uint32_t)index;
|
||||||
|
entry->image_id = index + 1;
|
||||||
|
fill_digest(entry->representation_sha256, (unsigned char)(0x11 * (index + 1)));
|
||||||
|
entry->width = index == 0 ? 4000 : 6000;
|
||||||
|
entry->height = index == 0 ? 2250 : 4000;
|
||||||
|
entry->fx = index == 0 ? 3000.0 : 4500.0;
|
||||||
|
entry->fy = index == 0 ? 3001.0 : 4502.0;
|
||||||
|
entry->cx = index == 0 ? 2000.0 : 3000.0;
|
||||||
|
entry->cy = index == 0 ? 1100.0 : 2000.0;
|
||||||
|
entry->k1 = index == 0 ? 0.01 : 0.02;
|
||||||
|
entry->k2 = index == 0 ? -0.01 : -0.02;
|
||||||
|
entry->p1 = 0.001;
|
||||||
|
entry->p2 = -0.001;
|
||||||
|
entry->support_images = 50;
|
||||||
|
entry->support_observations = 2000;
|
||||||
|
entry->reprojection_rmse_px = 0.4;
|
||||||
|
entry->maximum_parameter_delta = 0.01;
|
||||||
|
entry->validation_flags = LARDON3D_CALIBRATION_TOOLING_V2_VALIDATION_FLAGS;
|
||||||
|
}
|
||||||
|
evidence->groups = groups;
|
||||||
|
evidence->group_count = 2;
|
||||||
|
evidence->entry_count = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validation_cases(void) {
|
||||||
|
Lardon3DCalibrationToolingV2Evidence evidence;
|
||||||
|
Lardon3DCalibrationToolingV2Group groups[2];
|
||||||
|
Lardon3DCalibrationToolingV2Entry entries[2];
|
||||||
|
evidence_fixture(&evidence, groups, entries);
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_validate(&evidence) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_OK);
|
||||||
|
|
||||||
|
evidence.group_count = 1;
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_validate(&evidence) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED);
|
||||||
|
evidence.group_count = 2;
|
||||||
|
entries[1].image_id = entries[0].image_id;
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_validate(&evidence) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED);
|
||||||
|
entries[1].image_id = 2;
|
||||||
|
Lardon3DCalibrationToolingV2Group swapped[2] = {groups[1], groups[0]};
|
||||||
|
evidence.groups = swapped;
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_validate(&evidence) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED);
|
||||||
|
evidence.groups = groups;
|
||||||
|
entries[1].fx = NAN;
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_validate(&evidence) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_EVIDENCE_REJECTED);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool create_execution(const char *path, Lardon3DProjectDb **database,
|
||||||
|
Lardon3DProjectDbSelectedExecution *execution) {
|
||||||
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||||
|
CHECK(lardon3d_project_db_open(path, database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
lardon3d_project_db_close(*database);
|
||||||
|
*database = NULL;
|
||||||
|
CHECK(sql(path,
|
||||||
|
"INSERT INTO scansets(scanset_id,name,created_at,updated_at) VALUES(1,'v2',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,1,0,0,1,1,0,'GOOD'),"
|
||||||
|
"(1,2,1,1,0,1,0,100,100,100,100,1,1,0,0,1,1,0,'GOOD');"
|
||||||
|
"INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02');"
|
||||||
|
"INSERT INTO captures VALUES(1,1,1),(2,1,2);"
|
||||||
|
"INSERT INTO acquisition_campaign_captures VALUES(2,11,1),(2,12,2);"
|
||||||
|
"INSERT INTO image_assets VALUES"
|
||||||
|
"(1,X'1111111111111111111111111111111111111111111111111111111111111111',"
|
||||||
|
"'assets/images/11/1111111111111111111111111111111111111111111111111111111111111111',"
|
||||||
|
"1,1,1),"
|
||||||
|
"(2,X'2222222222222222222222222222222222222222222222222222222222222222',"
|
||||||
|
"'assets/images/22/2222222222222222222222222222222222222222222222222222222222222222',"
|
||||||
|
"1,1,2);"
|
||||||
|
"INSERT INTO images VALUES"
|
||||||
|
"(1,1,1,'one.jpg','/one.jpg',NULL,1),"
|
||||||
|
"(2,1,2,'two.jpg','/two.jpg',NULL,2);"
|
||||||
|
"INSERT INTO capture_images VALUES(1,1),(2,2);"));
|
||||||
|
CHECK(lardon3d_project_db_open(path, database, error) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
Lardon3DProjectDbSelectedExecutionItem items[2] = {
|
||||||
|
{.item_index = 0, .quality_group_id = 1, .campaign_group_id = 11,
|
||||||
|
.capture_id = 1,
|
||||||
|
.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE},
|
||||||
|
{.item_index = 1, .quality_group_id = 2, .campaign_group_id = 12,
|
||||||
|
.capture_id = 2,
|
||||||
|
.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE}};
|
||||||
|
CHECK(lardon3d_project_db_create_selected_execution(*database, 1, 2, items, 2, 10,
|
||||||
|
execution) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_record_selected_representation(
|
||||||
|
*database, execution->execution_id, 0, 1, 1) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_project_db_record_selected_representation(
|
||||||
|
*database, execution->execution_id, 1, 2, 2) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool malformed_artifact_is_prepublication(
|
||||||
|
const char *path, Lardon3DProjectDb *database, uint64_t execution_id,
|
||||||
|
const unsigned char artifact[ARTIFACT_SIZE]) {
|
||||||
|
unsigned char artifact_sha256[32];
|
||||||
|
Lardon3DCalibrationBootstrapV2Output output;
|
||||||
|
Lardon3DProjectDbSelectedExecution execution;
|
||||||
|
int count = -1;
|
||||||
|
memset(&output, 0xa5, sizeof(output));
|
||||||
|
CHECK(digest(artifact, ARTIFACT_SIZE, artifact_sha256));
|
||||||
|
CHECK(lardon3d_calibration_bootstrap_v2_import(
|
||||||
|
database, execution_id, artifact, ARTIFACT_SIZE, artifact_sha256, &output) ==
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT);
|
||||||
|
const unsigned char zero_output[sizeof(output)] = {0};
|
||||||
|
CHECK(memcmp(&output, zero_output, sizeof(output)) == 0);
|
||||||
|
CHECK(scalar(path, "SELECT COUNT(*) FROM sparse_calibrations", &count) && count == 0);
|
||||||
|
CHECK(scalar(path, "SELECT COUNT(*) FROM sparse_calibration_scopes", &count) && count == 0);
|
||||||
|
CHECK(lardon3d_project_db_load_selected_execution(database, execution_id, &execution) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
execution.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
|
||||||
|
!execution.has_calibration_scope);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool production_import(void) {
|
||||||
|
char directory[] = "/tmp/lardon3d-calibration-publication-v2-XXXXXX";
|
||||||
|
CHECK(mkdtemp(directory) != NULL);
|
||||||
|
char path[512];
|
||||||
|
CHECK(snprintf(path, sizeof(path), "%s/project.db", directory) > 0);
|
||||||
|
Lardon3DProjectDb *database = NULL;
|
||||||
|
Lardon3DProjectDbSelectedExecution execution;
|
||||||
|
CHECK(create_execution(path, &database, &execution));
|
||||||
|
|
||||||
|
Lardon3DCalibrationToolingV2Evidence evidence;
|
||||||
|
Lardon3DCalibrationToolingV2Group groups[2];
|
||||||
|
Lardon3DCalibrationToolingV2Entry entries[2];
|
||||||
|
evidence_fixture(&evidence, groups, entries);
|
||||||
|
unsigned char artifact_a[ARTIFACT_SIZE + 1], artifact_b[ARTIFACT_SIZE];
|
||||||
|
unsigned char hash_a[32], hash_b[32];
|
||||||
|
size_t size_a = 0, size_b = 0;
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_produce(
|
||||||
|
&evidence, artifact_a, ARTIFACT_SIZE, &size_a, hash_a) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_OK);
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_produce(
|
||||||
|
&evidence, artifact_b, sizeof(artifact_b), &size_b, hash_b) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_OK);
|
||||||
|
CHECK(size_a == ARTIFACT_SIZE && size_b == size_a &&
|
||||||
|
memcmp(artifact_a, artifact_b, size_a) == 0 && memcmp(hash_a, hash_b, 32) == 0);
|
||||||
|
|
||||||
|
/* These checksum-valid corruptions cross the production bootstrap parser
|
||||||
|
* directly. Offsets select fields in the two canonical single-member group
|
||||||
|
* records; they do not duplicate the parser's validation decisions. */
|
||||||
|
unsigned char malformed[ARTIFACT_SIZE];
|
||||||
|
const size_t first_member = ARTIFACT_HEADER_SIZE + GROUP_MEMBER_OFFSET;
|
||||||
|
const size_t second_member =
|
||||||
|
ARTIFACT_HEADER_SIZE + GROUP_RECORD_SIZE + GROUP_MEMBER_OFFSET;
|
||||||
|
memcpy(malformed, artifact_b, sizeof(malformed));
|
||||||
|
write_u64_le(malformed + first_member + MEMBER_FX_OFFSET, UINT64_C(0x7ff8000000000000));
|
||||||
|
CHECK(malformed_artifact_is_prepublication(
|
||||||
|
path, database, execution.execution_id, malformed));
|
||||||
|
|
||||||
|
memcpy(malformed, artifact_b, sizeof(malformed));
|
||||||
|
write_u32_le(malformed + second_member + MEMBER_SELECTED_INDEX_OFFSET, 0);
|
||||||
|
CHECK(malformed_artifact_is_prepublication(
|
||||||
|
path, database, execution.execution_id, malformed));
|
||||||
|
|
||||||
|
memcpy(malformed, artifact_b, sizeof(malformed));
|
||||||
|
write_u64_le(malformed + second_member + MEMBER_IMAGE_ID_OFFSET, 1);
|
||||||
|
CHECK(malformed_artifact_is_prepublication(
|
||||||
|
path, database, execution.execution_id, malformed));
|
||||||
|
|
||||||
|
memcpy(malformed, artifact_b, sizeof(malformed));
|
||||||
|
write_u32_le(malformed + second_member + MEMBER_SELECTED_INDEX_OFFSET, 2);
|
||||||
|
CHECK(malformed_artifact_is_prepublication(
|
||||||
|
path, database, execution.execution_id, malformed));
|
||||||
|
|
||||||
|
Lardon3DCalibrationBootstrapV2Output output;
|
||||||
|
unsigned char wrong_hash[32] = {0};
|
||||||
|
CHECK(lardon3d_calibration_bootstrap_v2_import(
|
||||||
|
database, execution.execution_id, artifact_a, size_a, wrong_hash, &output) ==
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_PROVENANCE_MISMATCH);
|
||||||
|
unsigned char trailing_hash[32];
|
||||||
|
artifact_a[size_a] = 0;
|
||||||
|
CHECK(digest(artifact_a, size_a + 1, trailing_hash));
|
||||||
|
CHECK(lardon3d_calibration_bootstrap_v2_import(
|
||||||
|
database, execution.execution_id, artifact_a, size_a + 1, trailing_hash, &output) ==
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT);
|
||||||
|
|
||||||
|
unsigned char reversed[ARTIFACT_SIZE], temporary[GROUP_RECORD_SIZE], reversed_hash[32];
|
||||||
|
memcpy(reversed, artifact_b, sizeof(reversed));
|
||||||
|
memcpy(temporary, reversed + 28, sizeof(temporary));
|
||||||
|
memcpy(reversed + 28, reversed + 28 + GROUP_RECORD_SIZE, GROUP_RECORD_SIZE);
|
||||||
|
memcpy(reversed + 28 + GROUP_RECORD_SIZE, temporary, GROUP_RECORD_SIZE);
|
||||||
|
CHECK(digest(reversed, sizeof(reversed), reversed_hash));
|
||||||
|
CHECK(lardon3d_calibration_bootstrap_v2_import(
|
||||||
|
database, execution.execution_id, reversed, sizeof(reversed), reversed_hash, &output) ==
|
||||||
|
LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT);
|
||||||
|
|
||||||
|
fill_digest(entries[1].representation_sha256, 0x33);
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_import(
|
||||||
|
database, execution.execution_id, &evidence, artifact_a, ARTIFACT_SIZE,
|
||||||
|
&size_a, &output) == LARDON3D_CALIBRATION_TOOLING_V2_IMPORT_ERROR);
|
||||||
|
fill_digest(entries[1].representation_sha256, 0x22);
|
||||||
|
int count = -1;
|
||||||
|
CHECK(scalar(path, "SELECT COUNT(*) FROM sparse_calibrations", &count) && count == 0);
|
||||||
|
CHECK(scalar(path, "SELECT COUNT(*) FROM sparse_calibration_scopes", &count) && count == 0);
|
||||||
|
CHECK(lardon3d_project_db_load_selected_execution(database, execution.execution_id,
|
||||||
|
&execution) == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
execution.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
|
||||||
|
!execution.has_calibration_scope);
|
||||||
|
|
||||||
|
entries[1].image_id = 99;
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_import(
|
||||||
|
database, execution.execution_id, &evidence, artifact_a, ARTIFACT_SIZE,
|
||||||
|
&size_a, &output) == LARDON3D_CALIBRATION_TOOLING_V2_IMPORT_ERROR);
|
||||||
|
entries[1].image_id = 2;
|
||||||
|
CHECK(scalar(path, "SELECT COUNT(*) FROM sparse_calibrations", &count) && count == 0);
|
||||||
|
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_produce(
|
||||||
|
&evidence, artifact_a, ARTIFACT_SIZE, &size_a, hash_a) ==
|
||||||
|
LARDON3D_CALIBRATION_TOOLING_V2_OK);
|
||||||
|
Lardon3DCalibrationBootstrapV2Result imported = lardon3d_calibration_bootstrap_v2_import(
|
||||||
|
database, execution.execution_id, artifact_a, size_a, hash_a, &output);
|
||||||
|
if (imported != LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK)
|
||||||
|
fprintf(stderr, "valid v2 bootstrap result=%d\n", (int)imported);
|
||||||
|
CHECK(imported == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK);
|
||||||
|
uint64_t scope_id = output.scope.scope_id;
|
||||||
|
CHECK(output.group_count == 2 && output.calibration_count == 2 && scope_id != 0);
|
||||||
|
CHECK(lardon3d_calibration_tooling_v2_import(
|
||||||
|
database, execution.execution_id, &evidence, artifact_a, ARTIFACT_SIZE,
|
||||||
|
NULL, &output) == LARDON3D_CALIBRATION_TOOLING_V2_OK &&
|
||||||
|
output.scope.scope_id == scope_id);
|
||||||
|
|
||||||
|
Lardon3DSparseCalibrationMember members[2];
|
||||||
|
size_t member_count = 0;
|
||||||
|
uint64_t next_image_id = 0;
|
||||||
|
CHECK(lardon3d_sparse_calibration_scope_list_members(
|
||||||
|
database, scope_id, 0, members, 2, &member_count, &next_image_id) ==
|
||||||
|
LARDON3D_PROJECT_DB_OK &&
|
||||||
|
member_count == 2 && next_image_id == 2 && members[0].image_id == 1 &&
|
||||||
|
members[1].image_id == 2);
|
||||||
|
Lardon3DSparseCalibration calibrations[2];
|
||||||
|
CHECK(lardon3d_sparse_calibration_load(database, members[0].calibration_id,
|
||||||
|
&calibrations[0]) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(lardon3d_sparse_calibration_load(database, members[1].calibration_id,
|
||||||
|
&calibrations[1]) == LARDON3D_PROJECT_DB_OK);
|
||||||
|
CHECK(memcmp(calibrations[0].provenance_fingerprint,
|
||||||
|
calibrations[1].provenance_fingerprint, 32) != 0);
|
||||||
|
CHECK(lardon3d_project_db_load_selected_execution(database, execution.execution_id,
|
||||||
|
&execution) == LARDON3D_PROJECT_DB_OK &&
|
||||||
|
execution.stage == LARDON3D_SELECTED_EXECUTION_READY &&
|
||||||
|
execution.has_calibration_scope && execution.calibration_scope_id == scope_id);
|
||||||
|
|
||||||
|
lardon3d_project_db_close(database);
|
||||||
|
CHECK(unlink(path) == 0);
|
||||||
|
CHECK(rmdir(directory) == 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
return validation_cases() && production_import() ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue