calibration: freeze tooling v1

This commit is contained in:
fy59 2026-09-01 17:45:02 +02:00
parent 591059f396
commit 9006e8ba15
6 changed files with 559 additions and 0 deletions

View file

@ -145,4 +145,32 @@ advances the selected-execution cursor itself, and retains its published `image_
generic Task progress can become durable. Its reservation owns one CPU thread, one I/O slot and a
conservative 2 GiB working allowance for the bounded 40 MP decoder and publication buffers only for
the callback lifetime; this operational admission bound is not a scientific Capture-count limit.
## Calibration Tooling v1
**PASS / FROZEN.** `calibration_tooling.h` is the bounded production bridge
between a completed external `CALIBRATION_SCIENCE_V1` evidence bundle and this
importer. It does not solve calibration, parse an unbounded user document, own
an acquisition session, or add a persistent schema. The caller supplies a
borrowed manifest bounded to 4,096 views and 4,096 selected image bindings,
with hashes for the target, optical state and four required evidence artifacts.
It declares and checks the frozen ChArUco family, `9 x 7` geometry,
`DICT_5X5_100`, 30.000 mm squares, 21.000 mm markers, 270.000 x 210.000 mm
active area and at least a 30.000 mm white border; the target hash binds the
corresponding immutable physical-evidence record.
Each view records an accepted/rejected decision and rejected views retain a
nonzero rejection reason; accepted statistics alone are evaluated. Each entry
carries the same optical-state manifest hash and remains in the exact selected
item order required by the importer (it is not reordered by image id).
The validator applies every hard Science v1 acceptance rule before allocating
or calling Project DB. The producer writes the exact existing `L3DCALB1` v1
little-endian record into caller-owned storage (at most 600,000 bytes), hashes
it deterministically, and invokes only
`lardon3d_calibration_bootstrap_import(...)`.
Invalid evidence therefore creates no calibration row and never reaches
`READY`. A valid exact retry reuses the frozen importers immutable
calibrations, scope and selected-execution attachment. Tooling stops at
`READY`; it never creates a Sparse SfM Task, and it cannot retro-calibrate the
historical S21 campaign.
A higher-level selected-execution coordinator remains separate scope.

View file

@ -147,6 +147,12 @@ provenance requis pour les **futures** campagnes connues-calibrées. Il ne
réhabilite aucune campagne historique : S21 Engine Bay reste définitivement
non rétro-calibrable. Voir [Calibration Science v1](../architecture/calibration_science_v1.md).
`CALIBRATION_TOOLING_V1=PASS/FROZEN` ajoute le pont opérationnel borné entre
un bundle d'évidence Science v1 déjà acquis et l'importeur immuable `L3DCALB1`
v1. Il valide les HARD REJECTS, produit l'artefact déterministe et s'arrête à
la transition `CALIBRATION → READY`; il ne résout aucune calibration, ne lance
pas Sparse SfM et ne rétro-calibre pas S21.
## PHOTO QUALITY TRIAGE / ACQUISITION SELECTION — PASS / FROZEN
L'étape qualité canonique implémentée se place après la découverte bornée, les

View file

@ -0,0 +1,141 @@
#ifndef LARDON3D_CALIBRATION_TOOLING_H
#define LARDON3D_CALIBRATION_TOOLING_H
#include <stddef.h>
#include <stdint.h>
#include <lardon3d/calibration_bootstrap.h>
#ifdef __cplusplus
extern "C" {
#endif
enum {
LARDON3D_CALIBRATION_TOOLING_VERSION = 1,
LARDON3D_CALIBRATION_TOOLING_MAX_VIEWS = 4096,
LARDON3D_CALIBRATION_TOOLING_MAX_COORDINATE_CHECKS = 81920,
LARDON3D_CALIBRATION_TOOLING_TARGET_MEASUREMENTS = 10,
LARDON3D_CALIBRATION_TOOLING_VALIDATION_FLAGS = 15,
LARDON3D_CALIBRATION_TOOLING_TARGET_CHARUCO_9X7_DICT_5X5_100 = 1,
};
typedef enum {
LARDON3D_CALIBRATION_TOOLING_OK = 0,
LARDON3D_CALIBRATION_TOOLING_INVALID_ARGUMENT,
LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED,
LARDON3D_CALIBRATION_TOOLING_CAPACITY,
LARDON3D_CALIBRATION_TOOLING_ENCODING_ERROR,
LARDON3D_CALIBRATION_TOOLING_IMPORT_ERROR,
} Lardon3DCalibrationToolingResult;
/* One solver-reported view. `quadrant` is 0..3 for frame quadrants and 4 for
* the centre. `distance_band` is the declared near/mid/far band 0..2.
* Rejected views retain their source identity and a nonzero rejection reason;
* only accepted views contribute to the frozen acceptance statistics. The
* manifest is caller-owned and borrowed only during validation/production. */
typedef struct {
unsigned char source_sha256[32];
uint32_t accepted;
uint32_t rejection_reason;
uint32_t holdout;
uint32_t quadrant;
uint32_t distance_band;
uint32_t orientation_degrees;
uint32_t target_corner_quadrant_mask;
uint32_t corner_count;
uint32_t residual_count;
uint32_t high_residual_count;
double target_occupancy;
double normal_angle_degrees;
double distance_metres;
double corner_rms_px;
double clipped_fraction;
double reprojection_rmse_px;
double maximum_residual_px;
} Lardon3DCalibrationToolingView;
/* Every selected image has exactly one artifact entry in selected-item order.
* `fit_parameters` are the deterministic 80% fit result used to verify `maximum_parameter_delta`; the
* published parameters are the complete-set result. */
typedef struct {
uint64_t image_id;
unsigned char representation_sha256[32];
unsigned char optical_state_sha256[32];
uint32_t width;
uint32_t height;
double fx, fy, cx, cy, k1, k2, p1, p2;
double fit_fx, fit_fy, fit_cx, fit_cy, fit_k1, fit_k2, fit_p1, fit_p2;
double repeated_parameters[3][8];
uint32_t support_images;
uint32_t support_observations;
double reprojection_rmse_px;
double maximum_parameter_delta;
uint32_t validation_flags;
} Lardon3DCalibrationToolingEntry;
typedef struct {
unsigned char source_sha256[32];
uint32_t orientation_degrees;
double dx_px;
double dy_px;
} Lardon3DCalibrationToolingCoordinateCheck;
/* This is an operational, bounded view of a completed Science v1 bundle. WHY:
* the solver remains external, so this API validates its immutable evidence
* rather than importing a solver-private format. CONTRACT: all pointers are
* borrowed; each digest is SHA-256; no field is persisted by this API except
* through the frozen bootstrap importer. INVARIANT: entries are in selected
* item order and never include Tracks, poses, or reconstruction results. */
typedef struct {
unsigned char target_sha256[32];
unsigned char optical_state_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];
uint32_t target_family;
uint32_t target_squares_x;
uint32_t target_squares_y;
double target_square_length_mm;
double target_marker_length_mm;
double target_active_width_mm;
double target_active_height_mm;
double target_white_border_mm;
double target_measurements_mm[LARDON3D_CALIBRATION_TOOLING_TARGET_MEASUREMENTS];
double measurement_resolution_mm;
double target_flatness_mm;
double holdout_rmse_px;
double holdout_maximum_residual_px;
uint32_t extra_distortion_coefficient_count;
const Lardon3DCalibrationToolingView *views;
size_t view_count;
const Lardon3DCalibrationToolingEntry *entries;
size_t entry_count;
const Lardon3DCalibrationToolingCoordinateCheck *coordinate_checks;
size_t coordinate_check_count;
} Lardon3DCalibrationToolingEvidence;
/* Validate all CALIBRATION_SCIENCE_V1 hard rejects. No allocations, DB access,
* solver execution, or persistent writes occur. */
Lardon3DCalibrationToolingResult lardon3d_calibration_tooling_validate(
const Lardon3DCalibrationToolingEvidence *evidence);
/* Encode exactly L3DCALB1 v1 into caller storage and return its SHA-256. The
* output is deterministic for identical evidence. `written` may be NULL. */
Lardon3DCalibrationToolingResult lardon3d_calibration_tooling_produce(
const Lardon3DCalibrationToolingEvidence *evidence, unsigned char *artifact,
size_t artifact_capacity, size_t *written, unsigned char artifact_sha256[32]);
/* Validate, produce, then invoke only the frozen production importer. A failed
* validation/encoding never reaches the DB. Import semantics, including its
* permitted immutable-row recovery behavior, remain owned by that importer. */
Lardon3DCalibrationToolingResult lardon3d_calibration_tooling_import(
Lardon3DProjectDb *database, uint64_t execution_id,
const Lardon3DCalibrationToolingEvidence *evidence, unsigned char *artifact,
size_t artifact_capacity, size_t *written, Lardon3DCalibrationBootstrapOutput *output);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -11,6 +11,8 @@ project(
],
)
cc = meson.get_compiler('c')
add_project_arguments(
'-D_POSIX_C_SOURCE=200809L',
'-Wpedantic',
@ -177,6 +179,7 @@ lardon3d_app = executable(
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/optical_profiles.c',
'src/calibration_bootstrap.c',
'src/calibration_tooling.c',
'src/sparse_sfm_geometry.cpp',
'src/sparse_sfm_incremental.cpp',
'src/sparse_sfm_bundle_adjustment.cpp',
@ -877,6 +880,21 @@ calibration_bootstrap_test = executable(
test('calibration-bootstrap', calibration_bootstrap_test, timeout: 30)
calibration_tooling_test = executable(
'test-calibration-tooling',
sources: [
'tests/test_calibration_tooling.c',
'src/calibration_tooling.c', 'src/calibration_bootstrap.c',
'src/project_db.c', 'src/project_db_sparse_sfm.c',
'src/task.c', 'src/resource_governor.c', 'src/resource_snapshot.c',
],
c_args: ['-DLARDON3D_PROJECT_DB_TESTING'],
include_directories: include_directories('include'),
dependencies: [threads, sqlite3, openssl, cc.find_library('m')],
)
test('calibration-tooling', calibration_tooling_test, timeout: 30)
optical_profiles_test = executable(
'test-optical-profiles',
sources: [

234
src/calibration_tooling.c Normal file
View file

@ -0,0 +1,234 @@
#include <lardon3d/calibration_tooling.h>
#include <math.h>
#include <openssl/evp.h>
#include <stdbool.h>
#include <string.h>
enum { kHeaderSize = 152, kEntrySize = 140, kMinimumViews = 40, kMinimumCorners = 1600 };
static bool nonzero(const unsigned char value[32]) {
unsigned char any = 0;
for (size_t i = 0; i < 32; ++i) any |= value[i];
return any != 0;
}
static bool finite_value(double value) { return isfinite(value); }
static void put_u32(unsigned char *p, uint32_t value) {
for (size_t i = 0; i < 4; ++i) p[i] = (unsigned char)(value >> (8u * i));
}
static void put_u64(unsigned char *p, uint64_t value) {
for (size_t i = 0; i < 8; ++i) p[i] = (unsigned char)(value >> (8u * i));
}
static void put_f64(unsigned char *p, double value) {
uint64_t bits = 0; memcpy(&bits, &value, sizeof(bits)); put_u64(p, bits);
}
static bool digest(const unsigned char *p, size_t n, unsigned char out[32]) {
unsigned int length = 0;
return EVP_Digest(p, n, out, &length, EVP_sha256(), NULL) == 1 && length == 32;
}
static bool parameters_valid(const Lardon3DCalibrationToolingEntry *v, bool fit) {
const double *p = fit ? &v->fit_fx : &v->fx;
return p[0] > 0.0 && p[1] > 0.0 && p[2] >= 0.0 && p[3] >= 0.0 &&
p[2] < (double)v->width && p[3] < (double)v->height &&
finite_value(p[0]) && finite_value(p[1]) && finite_value(p[2]) && finite_value(p[3]) &&
finite_value(p[4]) && finite_value(p[5]) && finite_value(p[6]) && finite_value(p[7]);
}
static int angle_class(double degrees) {
if (degrees < 20.0 || degrees > 60.0) return -1;
return degrees <= 35.0 ? 0 : (degrees <= 50.0 ? 1 : 2);
}
static unsigned int popcount4(uint32_t value) {
unsigned int count = 0;
for (unsigned int bit = 0; bit < 4; ++bit) count += (value >> bit) & 1u;
return count;
}
static bool projected_delta_ok(const Lardon3DCalibrationToolingEntry *v) {
static const double rays[5][2] = {{0, 0}, {-0.7, -0.7}, {0.7, -0.7},
{-0.7, 0.7}, {0.7, 0.7}};
const double *a = &v->fx, *b = &v->fit_fx;
double maximum = 0.0;
for (size_t i = 0; i < 5; ++i) {
double x = rays[i][0], y = rays[i][1], r2 = x*x + y*y;
double ax = x*(1+a[4]*r2+a[5]*r2*r2)+2*a[6]*x*y+a[7]*(r2+2*x*x);
double ay = y*(1+a[4]*r2+a[5]*r2*r2)+a[6]*(r2+2*y*y)+2*a[7]*x*y;
double bx = x*(1+b[4]*r2+b[5]*r2*r2)+2*b[6]*x*y+b[7]*(r2+2*x*x);
double by = y*(1+b[4]*r2+b[5]*r2*r2)+b[6]*(r2+2*y*y)+2*b[7]*x*y;
double delta = hypot(a[0]*ax+a[2] - (b[0]*bx+b[2]),
a[1]*ay+a[3] - (b[1]*by+b[3]));
if (!finite_value(delta)) return false;
if (delta > maximum) maximum = delta;
}
return finite_value(v->maximum_parameter_delta) &&
fabs(v->maximum_parameter_delta - maximum) <= 1e-12 && maximum <= 0.10;
}
Lardon3DCalibrationToolingResult lardon3d_calibration_tooling_validate(
const Lardon3DCalibrationToolingEvidence *e) {
if (!e || !e->views || !e->entries || !e->coordinate_checks || e->view_count > LARDON3D_CALIBRATION_TOOLING_MAX_VIEWS ||
e->entry_count == 0 || e->entry_count > 4096 || e->coordinate_check_count > LARDON3D_CALIBRATION_TOOLING_MAX_COORDINATE_CHECKS)
return LARDON3D_CALIBRATION_TOOLING_INVALID_ARGUMENT;
if (!nonzero(e->target_sha256) || !nonzero(e->optical_state_sha256) ||
!nonzero(e->solver_executable_sha256) || !nonzero(e->solver_configuration_sha256) ||
!nonzero(e->initialization_evidence_sha256) || !nonzero(e->validation_evidence_sha256) ||
e->target_family != LARDON3D_CALIBRATION_TOOLING_TARGET_CHARUCO_9X7_DICT_5X5_100 ||
e->target_squares_x != 9 || e->target_squares_y != 7 ||
!finite_value(e->target_square_length_mm) || e->target_square_length_mm != 30.0 ||
!finite_value(e->target_marker_length_mm) || e->target_marker_length_mm != 21.0 ||
!finite_value(e->target_active_width_mm) || e->target_active_width_mm != 270.0 ||
!finite_value(e->target_active_height_mm) || e->target_active_height_mm != 210.0 ||
!finite_value(e->target_white_border_mm) || e->target_white_border_mm < 30.0 ||
e->extra_distortion_coefficient_count != 0 || !finite_value(e->measurement_resolution_mm) ||
e->measurement_resolution_mm <= 0 || e->measurement_resolution_mm > 0.1 ||
!finite_value(e->target_flatness_mm) || e->target_flatness_mm < 0 || e->target_flatness_mm > 0.20)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
double lo = 30.0, hi = 30.0;
for (size_t i = 0; i < LARDON3D_CALIBRATION_TOOLING_TARGET_MEASUREMENTS; ++i) {
if (!finite_value(e->target_measurements_mm[i]) || fabs(e->target_measurements_mm[i] - 30.0) > 0.30)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
if (e->target_measurements_mm[i] < lo) lo = e->target_measurements_mm[i];
if (e->target_measurements_mm[i] > hi) hi = e->target_measurements_mm[i];
}
if (hi - lo > 0.20 || !finite_value(e->holdout_rmse_px) || !finite_value(e->holdout_maximum_residual_px) ||
e->holdout_rmse_px < 0 || e->holdout_maximum_residual_px < 0 ||
e->holdout_rmse_px > 0.75 || e->holdout_maximum_residual_px > 1.50)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
uint64_t corners = 0, residuals = 0, high = 0;
size_t accepted_views = 0;
uint32_t quadrant[5] = {0}, distance[3] = {0}, angles[3] = {0}, inclined = 0;
size_t fit_views = 0, holdout_views = 0, fit_quadrant[4] = {0};
double min_distance = INFINITY, max_distance = 0.0;
for (size_t i = 0; i < e->view_count; ++i) {
const Lardon3DCalibrationToolingView *v = &e->views[i];
if (!nonzero(v->source_sha256) || v->accepted > 1 || v->holdout > 1 ||
(!v->accepted && v->rejection_reason == 0) ||
(v->accepted && v->rejection_reason != 0))
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
if (!v->accepted) {
if (v->holdout != 0) return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
continue;
}
if (v->quadrant > 4 || v->distance_band > 2 ||
(v->orientation_degrees != 0 && v->orientation_degrees != 90 &&
v->orientation_degrees != 180 && v->orientation_degrees != 270) ||
popcount4(v->target_corner_quadrant_mask) < 3 ||
v->corner_count < 16 || !finite_value(v->target_occupancy) || v->target_occupancy < .20 || v->target_occupancy > .80 ||
!finite_value(v->normal_angle_degrees) || !finite_value(v->distance_metres) || v->distance_metres <= 0 ||
!finite_value(v->corner_rms_px) || v->corner_rms_px < 0 || v->corner_rms_px > .25 || !finite_value(v->clipped_fraction) || v->clipped_fraction < 0 || v->clipped_fraction > .01 ||
!finite_value(v->reprojection_rmse_px) || v->reprojection_rmse_px < 0 || v->reprojection_rmse_px > .75 ||
!finite_value(v->maximum_residual_px) || v->maximum_residual_px < 0 || v->maximum_residual_px > 1.50)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
if (v->high_residual_count > v->residual_count || UINT64_MAX - corners < v->corner_count || UINT64_MAX - residuals < v->residual_count || UINT64_MAX - high < v->high_residual_count)
return LARDON3D_CALIBRATION_TOOLING_CAPACITY;
++accepted_views;
corners += v->corner_count; residuals += v->residual_count; high += v->high_residual_count;
++quadrant[v->quadrant]; ++distance[v->distance_band];
int class_index = angle_class(v->normal_angle_degrees);
if (class_index >= 0) { ++angles[class_index]; ++inclined; }
if (v->holdout) ++holdout_views;
else { ++fit_views; if (v->quadrant < 4) ++fit_quadrant[v->quadrant]; }
if (v->distance_metres < min_distance) min_distance = v->distance_metres;
if (v->distance_metres > max_distance) max_distance = v->distance_metres;
}
if (accepted_views < kMinimumViews || corners < kMinimumCorners || quadrant[0] < 6 || quadrant[1] < 6 || quadrant[2] < 6 || quadrant[3] < 6 || quadrant[4] < 8 ||
distance[0] < 8 || distance[1] < 8 || distance[2] < 8 || max_distance / min_distance < 1.5 ||
inclined < 24 || angles[0] < 6 || angles[1] < 6 || angles[2] < 6 || fit_views < 32 || holdout_views < 8 ||
fit_quadrant[0] < 4 || fit_quadrant[1] < 4 || fit_quadrant[2] < 4 || fit_quadrant[3] < 4 ||
residuals == 0 || high * 100 > residuals)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
for (size_t i = 0; i < e->view_count; ++i) {
const Lardon3DCalibrationToolingView *v = &e->views[i];
if (!v->accepted) continue;
size_t rank = 0;
int class_index = angle_class(v->normal_angle_degrees);
for (size_t other = 0; other < e->view_count; ++other) {
const Lardon3DCalibrationToolingView *candidate = &e->views[other];
if (!candidate->accepted || candidate->quadrant != v->quadrant ||
candidate->distance_band != v->distance_band ||
angle_class(candidate->normal_angle_degrees) != class_index) continue;
int compare = memcmp(candidate->source_sha256, v->source_sha256, 32);
if (compare == 0 && other != i) return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
if (compare < 0) ++rank;
}
if (v->holdout != (rank % 5 == 4)) return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
}
for (size_t i = 0; i < e->coordinate_check_count; ++i) {
const Lardon3DCalibrationToolingCoordinateCheck *c = &e->coordinate_checks[i];
if (!nonzero(c->source_sha256) || (c->orientation_degrees != 0 && c->orientation_degrees != 90 &&
c->orientation_degrees != 180 && c->orientation_degrees != 270) ||
!finite_value(c->dx_px) || !finite_value(c->dy_px) || fabs(c->dx_px) > .01 || fabs(c->dy_px) > .01)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
}
for (size_t view_index = 0; view_index < e->view_count; ++view_index) {
const Lardon3DCalibrationToolingView *v = &e->views[view_index];
if (!v->accepted) continue;
size_t matches = 0;
for (size_t check_index = 0; check_index < e->coordinate_check_count; ++check_index) {
const Lardon3DCalibrationToolingCoordinateCheck *c = &e->coordinate_checks[check_index];
if (memcmp(c->source_sha256, v->source_sha256, 32) == 0) {
if (c->orientation_degrees != v->orientation_degrees)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
++matches;
}
}
if (matches < 20) return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
}
for (size_t i = 0; i < e->entry_count; ++i) {
const Lardon3DCalibrationToolingEntry *v = &e->entries[i];
if (!v->image_id || !nonzero(v->representation_sha256) ||
memcmp(v->optical_state_sha256, e->optical_state_sha256, 32) != 0 ||
!v->width || !v->height ||
!parameters_valid(v, false) || !parameters_valid(v, true) || !v->support_images || !v->support_observations ||
!finite_value(v->reprojection_rmse_px) || v->reprojection_rmse_px < 0 || v->reprojection_rmse_px > .50 ||
v->validation_flags != LARDON3D_CALIBRATION_TOOLING_VALIDATION_FLAGS || !projected_delta_ok(v))
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
for (size_t previous = 0; previous < i; ++previous)
if (v->image_id == e->entries[previous].image_id)
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
const double published[8] = {v->fx,v->fy,v->cx,v->cy,v->k1,v->k2,v->p1,v->p2};
for (size_t repeat = 0; repeat < 3; ++repeat)
for (size_t parameter = 0; parameter < 8; ++parameter)
if (!finite_value(v->repeated_parameters[repeat][parameter]) ||
v->repeated_parameters[repeat][parameter] != published[parameter])
return LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED;
}
return LARDON3D_CALIBRATION_TOOLING_OK;
}
Lardon3DCalibrationToolingResult lardon3d_calibration_tooling_produce(
const Lardon3DCalibrationToolingEvidence *e, unsigned char *artifact,
size_t capacity, size_t *written, unsigned char hash[32]) {
if (written) *written = 0;
if (!artifact || !hash) return LARDON3D_CALIBRATION_TOOLING_INVALID_ARGUMENT;
Lardon3DCalibrationToolingResult valid = lardon3d_calibration_tooling_validate(e);
if (valid != LARDON3D_CALIBRATION_TOOLING_OK) return valid;
if (e->entry_count > (SIZE_MAX - kHeaderSize) / kEntrySize) return LARDON3D_CALIBRATION_TOOLING_CAPACITY;
size_t size = kHeaderSize + e->entry_count * kEntrySize;
if (size > LARDON3D_CALIBRATION_BOOTSTRAP_MAX_BYTES || capacity < size) return LARDON3D_CALIBRATION_TOOLING_CAPACITY;
size_t at = 0; memcpy(artifact + at, "L3DCALB1", 8); at += 8;
put_u32(artifact + at, 1); at += 4; put_u32(artifact + at, 1); at += 4; put_u32(artifact + at, 1); at += 4; put_u32(artifact + at, (uint32_t)e->entry_count); at += 4;
memcpy(artifact + at, e->solver_executable_sha256, 32); at += 32; memcpy(artifact + at, e->solver_configuration_sha256, 32); at += 32;
memcpy(artifact + at, e->initialization_evidence_sha256, 32); at += 32; memcpy(artifact + at, e->validation_evidence_sha256, 32); at += 32;
for (size_t i = 0; i < e->entry_count; ++i) {
const Lardon3DCalibrationToolingEntry *v = &e->entries[i];
put_u64(artifact + at, v->image_id); at += 8; memcpy(artifact + at, v->representation_sha256, 32); at += 32;
put_u32(artifact + at, v->width); at += 4; put_u32(artifact + at, v->height); at += 4;
const double values[8] = {v->fx,v->fy,v->cx,v->cy,v->k1,v->k2,v->p1,v->p2};
for (size_t j = 0; j < 8; ++j) { put_f64(artifact + at, values[j]); at += 8; }
put_u32(artifact + at, v->support_images); at += 4; put_u32(artifact + at, v->support_observations); at += 4;
put_f64(artifact + at, v->reprojection_rmse_px); at += 8; put_f64(artifact + at, v->maximum_parameter_delta); at += 8;
put_u32(artifact + at, v->validation_flags); at += 4;
}
if (at != size || !digest(artifact, size, hash)) return LARDON3D_CALIBRATION_TOOLING_ENCODING_ERROR;
if (written) *written = size;
return LARDON3D_CALIBRATION_TOOLING_OK;
}
Lardon3DCalibrationToolingResult lardon3d_calibration_tooling_import(
Lardon3DProjectDb *db, uint64_t execution_id, const Lardon3DCalibrationToolingEvidence *e,
unsigned char *artifact, size_t capacity, size_t *written, Lardon3DCalibrationBootstrapOutput *out) {
unsigned char hash[32];
Lardon3DCalibrationToolingResult result = lardon3d_calibration_tooling_produce(e, artifact, capacity, written, hash);
if (result != LARDON3D_CALIBRATION_TOOLING_OK || !db || !execution_id || !out) return result == LARDON3D_CALIBRATION_TOOLING_OK ? LARDON3D_CALIBRATION_TOOLING_INVALID_ARGUMENT : result;
return lardon3d_calibration_bootstrap_import(db, execution_id, artifact, *written, hash, out) == LARDON3D_CALIBRATION_BOOTSTRAP_OK ?
LARDON3D_CALIBRATION_TOOLING_OK : LARDON3D_CALIBRATION_TOOLING_IMPORT_ERROR;
}

View file

@ -0,0 +1,132 @@
#include <sqlite3.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <lardon3d/calibration_tooling.h>
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "tooling failure %d: %s\n", __LINE__, #x); return false; } } while (0)
static bool sql(const char *path, const char *text) {
sqlite3 *db = NULL; if (sqlite3_open(path, &db) != SQLITE_OK) return false;
int rc = sqlite3_exec(db, text, NULL, NULL, NULL); return sqlite3_close(db) == SQLITE_OK && rc == SQLITE_OK;
}
static bool scalar(const char *path, const char *text, int *value) {
sqlite3 *db = NULL; sqlite3_stmt *statement = NULL;
if (sqlite3_open(path, &db) != SQLITE_OK || sqlite3_prepare_v2(db, text, -1, &statement, NULL) != SQLITE_OK) {
if (statement) sqlite3_finalize(statement);
if (db) sqlite3_close(db);
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(db) == SQLITE_OK && ok;
}
static void hashes(unsigned char value[32], unsigned char seed) { memset(value, seed, 32); }
static void params(double output[8]) { const double p[8] = {3000,3001,2000,1100,.01,-.01,.001,-.001}; memcpy(output,p,sizeof(p)); }
/* Synthetic only: these values model an already-completed external session and
* never represent a physical calibration or a real project. */
static void fixture(Lardon3DCalibrationToolingEvidence *e, Lardon3DCalibrationToolingView views[60],
Lardon3DCalibrationToolingEntry entries[1], Lardon3DCalibrationToolingCoordinateCheck coordinates[1200]) {
memset(e, 0, sizeof(*e)); memset(views, 0, sizeof(Lardon3DCalibrationToolingView)*60);
memset(entries, 0, sizeof(Lardon3DCalibrationToolingEntry)); memset(coordinates, 0, sizeof(Lardon3DCalibrationToolingCoordinateCheck)*1200);
hashes(e->target_sha256,1); hashes(e->optical_state_sha256,2); hashes(e->solver_executable_sha256,3); hashes(e->solver_configuration_sha256,4); hashes(e->initialization_evidence_sha256,5); hashes(e->validation_evidence_sha256,6);
e->target_family=LARDON3D_CALIBRATION_TOOLING_TARGET_CHARUCO_9X7_DICT_5X5_100; e->target_squares_x=9; e->target_squares_y=7;
e->target_square_length_mm=30; e->target_marker_length_mm=21; e->target_active_width_mm=270; e->target_active_height_mm=210; e->target_white_border_mm=30;
for (size_t i=0;i<10;++i) e->target_measurements_mm[i]=30.0;
e->measurement_resolution_mm=.1; e->target_flatness_mm=.1; e->holdout_rmse_px=.4; e->holdout_maximum_residual_px=.8;
for (size_t i=0;i<60;++i) {
static const uint32_t quadrants[10]={0,0,1,1,2,2,3,3,4,4};
static const uint32_t distances[10]={0,1,2,0,1,2,0,1,2,0};
static const double angles[10]={25,40,55,25,40,55,25,40,55,25};
size_t group=i/6; hashes(views[i].source_sha256,(unsigned char)(i+20)); views[i].accepted=1; views[i].holdout=(i%6)==4; views[i].quadrant=quadrants[group];
views[i].distance_band=distances[group]; views[i].target_corner_quadrant_mask=7; views[i].corner_count=40; views[i].residual_count=40; views[i].target_occupancy=.4;
views[i].normal_angle_degrees=angles[group]; views[i].distance_metres=1.0+(double)distances[group]*.3;
views[i].corner_rms_px=.1; views[i].clipped_fraction=0; views[i].reprojection_rmse_px=.4; views[i].maximum_residual_px=.8;
}
entries[0].image_id=1; hashes(entries[0].representation_sha256,0x11); hashes(entries[0].optical_state_sha256,2); entries[0].width=4000; entries[0].height=2250;
double p[8]; params(p); memcpy(&entries[0].fx,p,sizeof(p)); memcpy(&entries[0].fit_fx,p,sizeof(p));
for (size_t repeat=0;repeat<3;++repeat) memcpy(entries[0].repeated_parameters[repeat],p,sizeof(p));
entries[0].support_images=60; entries[0].support_observations=2400; entries[0].reprojection_rmse_px=.4; entries[0].maximum_parameter_delta=0; entries[0].validation_flags=15;
for(size_t i=0;i<1200;++i) { memcpy(coordinates[i].source_sha256,views[i/20].source_sha256,32); coordinates[i].orientation_degrees=0; coordinates[i].dx_px=.001; coordinates[i].dy_px=-.001; }
e->views=views; e->view_count=60; e->entries=entries; e->entry_count=1; e->coordinate_checks=coordinates; e->coordinate_check_count=1200;
}
static bool rejects_major_cases(void) {
Lardon3DCalibrationToolingEvidence e; Lardon3DCalibrationToolingView v[60]; Lardon3DCalibrationToolingEntry x[1]; Lardon3DCalibrationToolingCoordinateCheck c[1200];
fixture(&e,v,x,c); CHECK(lardon3d_calibration_tooling_validate(&e)==LARDON3D_CALIBRATION_TOOLING_OK);
e.view_count=39; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); e.view_count=60;
v[0].corner_count=1; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); v[0].corner_count=40;
for (size_t i=0;i<3;++i) v[i].quadrant=4;
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
for (size_t i=0;i<3;++i) v[i].quadrant=0;
for (size_t i=0;i<9;++i) v[i*3].normal_angle_degrees=1;
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
for (size_t i=0;i<9;++i) v[i*3].normal_angle_degrees=25;
for (size_t i=0;i<7;++i) v[i*3].distance_band=1;
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
for (size_t i=0;i<7;++i) v[i*3].distance_band=0;
e.target_marker_length_mm=20; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); e.target_marker_length_mm=21;
e.target_measurements_mm[0]=31; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); e.target_measurements_mm[0]=30;
v[0].corner_rms_px=.3; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); v[0].corner_rms_px=.1;
x[0].reprojection_rmse_px=.6; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); x[0].reprojection_rmse_px=.4;
v[0].reprojection_rmse_px=.8; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); v[0].reprojection_rmse_px=.4;
v[0].maximum_residual_px=2; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); v[0].maximum_residual_px=.8;
v[0].high_residual_count=40; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); v[0].high_residual_count=0;
e.holdout_rmse_px=.8; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); e.holdout_rmse_px=.4;
x[0].repeated_parameters[1][0]+=1; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); x[0].repeated_parameters[1][0]-=1;
x[0].maximum_parameter_delta=.2; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); x[0].maximum_parameter_delta=0;
fixture(&e,v,x,c); for (size_t i=18;i<60;++i) v[i].normal_angle_degrees=0;
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
fixture(&e,v,x,c); v[4].holdout=0;
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
fixture(&e,v,x,c); v[0].corner_rms_px=-.1;
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
fixture(&e,v,x,c); x[0].reprojection_rmse_px=-.1;
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
fixture(&e,v,x,c); memset(e.solver_configuration_sha256,0,32);
CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
fixture(&e,v,x,c);
v[0].accepted=0; v[0].rejection_reason=7; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); v[0].accepted=1; v[0].rejection_reason=0;
memset(e.optical_state_sha256,0,32); CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); hashes(e.optical_state_sha256,2);
e.coordinate_check_count=1199; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); e.coordinate_check_count=1200;
x[0].width=0; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); x[0].width=4000;
x[0].fx=0; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); x[0].fx=3000;
x[0].cx=4000; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK); x[0].cx=2000;
e.extra_distortion_coefficient_count=1; CHECK(lardon3d_calibration_tooling_validate(&e)!=LARDON3D_CALIBRATION_TOOLING_OK);
return true;
}
static bool run(void) {
CHECK(rejects_major_cases());
Lardon3DCalibrationToolingEvidence e; Lardon3DCalibrationToolingView views[60]; Lardon3DCalibrationToolingEntry entries[1]; Lardon3DCalibrationToolingCoordinateCheck coords[1200]; fixture(&e,views,entries,coords);
unsigned char a[292], b[292], ha[32], hb[32]; size_t na=0, nb=0;
CHECK(lardon3d_calibration_tooling_produce(&e,a,sizeof(a),&na,ha)==LARDON3D_CALIBRATION_TOOLING_OK);
CHECK(lardon3d_calibration_tooling_produce(&e,b,sizeof(b),&nb,hb)==LARDON3D_CALIBRATION_TOOLING_OK && na==292 && na==nb && memcmp(a,b,na)==0 && memcmp(ha,hb,32)==0);
CHECK(lardon3d_calibration_tooling_produce(&e,b,291,&nb,hb)==LARDON3D_CALIBRATION_TOOLING_CAPACITY);
char dir[]="/tmp/lardon3d-calibration-tooling-XXXXXX"; CHECK(mkdtemp(dir)); char path[512]; CHECK(snprintf(path,sizeof(path),"%s/project.db",dir)>0);
Lardon3DProjectDb *db=NULL; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; CHECK(lardon3d_project_db_open(path,&db,error)==LARDON3D_PROJECT_DB_OK); lardon3d_project_db_close(db);
CHECK(sql(path,"INSERT INTO scansets VALUES(1,'s',1,1); INSERT INTO tasks VALUES(1,'q','photo_quality.triage',1,5,5,100,1,0,0,0,0,1); INSERT INTO tasks VALUES(2,'c','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'); INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02'); INSERT INTO captures VALUES(1,1,1); INSERT INTO acquisition_campaign_captures VALUES(2,2,1); INSERT INTO image_assets VALUES(1,X'1111111111111111111111111111111111111111111111111111111111111111','assets/images/11/1111111111111111111111111111111111111111111111111111111111111111',1,1,1); INSERT INTO images VALUES(1,1,1,'a.jpg','/a.jpg',NULL,1); INSERT INTO capture_images VALUES(1,1);"));
CHECK(lardon3d_project_db_open(path,&db,error)==LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbSelectedExecutionItem item={.item_index=0,.quality_group_id=1,.campaign_group_id=2,.capture_id=1,.representation_source=LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE}; Lardon3DProjectDbSelectedExecution execution;
CHECK(lardon3d_project_db_create_selected_execution(db,1,2,&item,1,1,&execution)==LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_record_selected_representation(db,execution.execution_id,0,1,1)==LARDON3D_PROJECT_DB_OK);
Lardon3DProjectDbSelectedExecutionItem selected; Lardon3DProjectDbImage image; Lardon3DProjectDbImageAsset asset;
CHECK(lardon3d_project_db_load_selected_execution_item(db,execution.execution_id,0,&selected)==LARDON3D_PROJECT_DB_OK && selected.has_image && selected.image_id==1);
CHECK(lardon3d_project_db_load_image(db,1,&image,&asset)==LARDON3D_PROJECT_DB_OK && memcmp(asset.sha256,entries[0].representation_sha256,32)==0);
entries[0].fx=0;
CHECK(lardon3d_calibration_tooling_import(db,execution.execution_id,&e,a,sizeof(a),&na,&(Lardon3DCalibrationBootstrapOutput){0})==LARDON3D_CALIBRATION_TOOLING_SCIENCE_REJECTED);
entries[0].fx=3000;
lardon3d_project_db_close(db);
int count=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_open(path,&db,error)==LARDON3D_PROJECT_DB_OK);
Lardon3DCalibrationBootstrapOutput output; CHECK(lardon3d_calibration_tooling_import(db,execution.execution_id,&e,a,sizeof(a),&na,&output)==LARDON3D_CALIBRATION_TOOLING_OK); uint64_t scope=output.scope.scope_id;
CHECK(lardon3d_calibration_tooling_import(db,execution.execution_id,&e,a,sizeof(a),&na,&output)==LARDON3D_CALIBRATION_TOOLING_OK && output.scope.scope_id==scope);
Lardon3DSparseCalibrationMember member; size_t member_count=0; uint64_t next=0;
CHECK(lardon3d_sparse_calibration_scope_list_members(db,scope,0,&member,1,&member_count,&next)==LARDON3D_PROJECT_DB_OK && member_count==1 && next==1 && member.image_id==1);
CHECK(lardon3d_project_db_load_selected_execution(db,execution.execution_id,&execution)==LARDON3D_PROJECT_DB_OK && execution.stage==LARDON3D_SELECTED_EXECUTION_READY && execution.calibration_scope_id==scope);
lardon3d_project_db_close(db); CHECK(unlink(path)==0); CHECK(rmdir(dir)==0); return true;
}
int main(void) { return run()?EXIT_SUCCESS:EXIT_FAILURE; }