feat: publish heterogeneous calibration groups

This commit is contained in:
fy59 2026-09-03 13:08:36 +02:00
parent 8fee775586
commit a77d095a85
13 changed files with 1163 additions and 25 deletions

View file

@ -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)

View file

@ -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.

View 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.

View file

@ -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.

View 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

View 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

View file

@ -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: [

View file

@ -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

View file

@ -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

View file

@ -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

View 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;
}

View 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;
}

View 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;
}