feat: materialize calibration workflow evidence

This commit is contained in:
fy59 2026-09-03 10:17:00 +02:00
parent e1cf83a158
commit 6a670fd9e9
9 changed files with 1360 additions and 21 deletions

View file

@ -5,7 +5,8 @@
```text ```text
CALIBRATION_WORKFLOW=IN_PROGRESS CALIBRATION_WORKFLOW=IN_PROGRESS
CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN
CURRENT_WORKFLOW_NEXT=EVIDENCE_MATERIALIZATION_V1 CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN
CURRENT_WORKFLOW_NEXT=SELECTED_EXECUTION_BINDING_V1
``` ```
## Authority ## Authority
@ -84,6 +85,60 @@ Input Boundary v1 does not:
- invoke Calibration Bootstrap; - invoke Calibration Bootstrap;
- change selected-execution state. - change selected-execution state.
## Evidence Materialization v1
`CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN`.
The implementation is additive:
```text
src/calibration_workflow_materialize.cpp
tests/test_calibration_workflow_materialize.cpp
```
It consumes only inputs that first pass Input Boundary v1 and performs no
Project DB access or mutation.
The caller owns bounded arrays for materialized views and coordinate checks.
On success the output borrows those arrays and retains:
- exact target generator SHA-256 and physical target measurements;
- measured white border and categorical planarity evidence;
- exact optical-state SHA-256;
- exact solver executable and configuration SHA-256;
- exact accepted/rejected per-view classifications and rejection reasons;
- hold-out assignment, frame region, distance band and target coverage;
- retained per-view residual counts and metrics;
- coordinate-equivalence checks derived from the retained session points;
- all three exact full-solve parameter vectors;
- exact fit parameter vector;
- support image/observation counts;
- global RMSE, maximum residual and high-residual fraction;
- hold-out RMSE and maximum residual;
- maximum parameter delta and `validation_flags=0x0f`.
The stage consumes published solver evidence; it does not reclassify views,
rerun calibration, average repeated solves or manufacture missing values.
`initialization_evidence_sha256` is the exact `session.l3dcal` SHA-256.
`validation_evidence_sha256` is deterministic and domain separated:
```text
SHA256(
ASCII("L3DCAL_WORKFLOW_VALIDATION_V1\n")
|| detection_sha256_raw32
|| solve_sha256_raw32
|| evidence_sha256_raw32
|| producer_sha256_raw32
)
```
This boundary deliberately does not construct per-campaign
`Lardon3DCalibrationToolingEntry` rows. Those rows require Project DB proof of
the selected image identities, representation bytes/dimensions and explicit
Capture optical assignments, which belongs to Selected Execution Binding v1.
## Campaign optical-state evidence ## Campaign optical-state evidence
Project DB v23 retains exact explicit optical configuration identity, including Project DB v23 retains exact explicit optical configuration identity, including
@ -102,21 +157,25 @@ Absence or disagreement remains `CALIBRATION_UNAVAILABLE`.
## Current next boundary ## Current next boundary
```text ```text
CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1 CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1
``` ```
The next stage parses the already validated bundle into bounded in-memory The next stage may read Project DB, but must not mutate it. It must:
Science v1 evidence:
- exact per-view evidence; - load the exact selected execution and require the calibration/ready stage
- exact repeated full-solve parameters; appropriate to the existing FROZEN contract;
- fit parameters; - require exact selected item count and order;
- global and hold-out validation metrics; - match every campaign-state Capture row to the selected item;
- coordinate-equivalence evidence; - load each Capture's explicit v23 optical assignment and require the exact
- immutable provenance digests. declared optical configuration;
- load every selected image/asset identity;
- read the exact managed representation as a bounded regular file;
- require asset byte size and SHA-256 equality;
- decode the exact geometric representation and require oriented dimensions
compatible with the calibration evidence;
- construct the per-image `Lardon3DCalibrationToolingEntry` rows without
changing any scientific value.
It still performs no Project DB mutation. Only after that read-only binding passes may a final workflow boundary invoke
the FROZEN Tooling/Bootstrap path and transition the selected execution to
Only after that boundary passes may the workflow bind the exact selected truthful `READY`.
execution, campaign representations and optical assignments and invoke the
FROZEN Tooling/Bootstrap path.

View file

@ -529,6 +529,8 @@ The solver session contract now also requires explicit measured `white_border >=
`CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN`: the workflow now has a bounded non-mutating input boundary for session, solver bundle and campaign optical-state evidence. It rejects special/symlink/oversize files, invalid JSON, provenance digest mismatches and incompatible optical state. The next boundary is Evidence Materialization v1. `CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN`: the workflow now has a bounded non-mutating input boundary for session, solver bundle and campaign optical-state evidence. It rejects special/symlink/oversize files, invalid JSON, provenance digest mismatches and incompatible optical state. The next boundary is Evidence Materialization v1.
`CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN`: the validated external calibration inputs are now materialized into bounded Science v1 target, per-view, coordinate, repeated-solve, fit, residual, hold-out and provenance evidence without Project DB access or mutation. The next boundary is exact Selected Execution Binding v1 before any Tooling/Bootstrap import.
A bounded Tooling correction aligned planarity handling with Calibration Science v1: Science v1 A bounded Tooling correction aligned planarity handling with Calibration Science v1: Science v1
defines a categorical physical planarity attestation, not a numeric flatness threshold. Tooling defines a categorical physical planarity attestation, not a numeric flatness threshold. Tooling
therefore rejects invented finite `target_flatness_mm` values. `L3DCALB1` v1 and Calibration therefore rejects invented finite `target_flatness_mm` values. `L3DCALB1` v1 and Calibration

View file

@ -4,6 +4,8 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <lardon3d/calibration_tooling.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -72,6 +74,69 @@ Lardon3DCalibrationWorkflowResult lardon3d_calibration_workflow_validate_input_b
const Lardon3DCalibrationWorkflowInputFiles *files, const Lardon3DCalibrationWorkflowInputFiles *files,
Lardon3DCalibrationWorkflowInputBoundary *boundary); Lardon3DCalibrationWorkflowInputBoundary *boundary);
typedef enum {
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_SOURCE_SIZE = 1,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_DECODE = 2,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_DECODED_DIMENSIONS = 3,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_INSUFFICIENT_CHARUCO = 4,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_INVALID_CHARUCO_ID = 5,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_OCCUPANCY = 6,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_TARGET_PHYSICAL_QUADRANTS = 7,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_CLIPPING = 8,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_PRE_SOLVE_CORNER_RMS = 9,
LARDON3D_CALIBRATION_WORKFLOW_REJECTION_COORDINATE_EQUIVALENCE = 10,
} Lardon3DCalibrationWorkflowRejectionReason;
typedef struct {
Lardon3DCalibrationWorkflowInputBoundary boundary;
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 Lardon3DCalibrationToolingCoordinateCheck *coordinate_checks;
size_t coordinate_check_count;
double repeated_parameters[3][8];
double fit_parameters[8];
uint32_t support_images;
uint32_t support_observations;
double reprojection_rmse_px;
double maximum_residual_px;
double high_residual_fraction;
double maximum_parameter_delta;
uint32_t validation_flags;
} Lardon3DCalibrationWorkflowExternalEvidence;
/* Materialize the already validated external session and solver bundle into
* bounded Science-v1 evidence. Caller owns `views` and `coordinate_checks`;
* output borrows those arrays on success. This stage performs no Project DB
* access, no Tooling import and no selected-execution mutation. */
Lardon3DCalibrationWorkflowResult
lardon3d_calibration_workflow_materialize_external_evidence(
const Lardon3DCalibrationWorkflowInputFiles *files,
Lardon3DCalibrationToolingView *views, size_t view_capacity,
Lardon3DCalibrationToolingCoordinateCheck *coordinate_checks,
size_t coordinate_check_capacity,
Lardon3DCalibrationWorkflowExternalEvidence *output);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -181,6 +181,7 @@ lardon3d_app = executable(
'src/calibration_bootstrap.c', 'src/calibration_bootstrap.c',
'src/calibration_tooling.c', 'src/calibration_tooling.c',
'src/calibration_workflow.cpp', 'src/calibration_workflow.cpp',
'src/calibration_workflow_materialize.cpp',
'src/sparse_sfm_geometry.cpp', 'src/sparse_sfm_geometry.cpp',
'src/sparse_sfm_incremental.cpp', 'src/sparse_sfm_incremental.cpp',
'src/sparse_sfm_bundle_adjustment.cpp', 'src/sparse_sfm_bundle_adjustment.cpp',
@ -908,6 +909,24 @@ calibration_workflow_test = executable(
test('calibration-workflow', calibration_workflow_test, timeout: 30) test('calibration-workflow', calibration_workflow_test, timeout: 30)
calibration_workflow_materialization_test = executable(
'test-calibration-workflow-materialization',
sources: [
'tests/test_calibration_workflow_materialize.cpp',
'src/calibration_workflow.cpp',
'src/calibration_workflow_materialize.cpp',
],
include_directories: include_directories('include'),
dependencies: [openssl],
)
test(
'calibration-workflow-materialization',
calibration_workflow_materialization_test,
timeout: 30,
)
optical_profiles_test = executable( optical_profiles_test = executable(
'test-optical-profiles', 'test-optical-profiles',
sources: [ sources: [

View file

@ -6,7 +6,7 @@
CURRENT_PROJECT_DB_SCHEMA=v25 CURRENT_PROJECT_DB_SCHEMA=v25
PRODUCTION_TASK_KINDS=16 PRODUCTION_TASK_KINDS=16
USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS
CURRENT_IMPLEMENTATION_CURSOR=1_CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION CURRENT_IMPLEMENTATION_CURSOR=1_CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING
``` ```
## Authority ## Authority
@ -32,6 +32,7 @@ Calibration solver producer identity PASS/FROZEN
Calibration solver per-view evidence PASS/FROZEN Calibration solver per-view evidence PASS/FROZEN
Calibration solver bundle repair PASS/FROZEN Calibration solver bundle repair PASS/FROZEN
Calibration workflow input boundary PASS/FROZEN Calibration workflow input boundary PASS/FROZEN
Calibration workflow evidence materialization PASS/FROZEN
Calibration Tooling planarity alignment PASS/FROZEN Calibration Tooling planarity alignment PASS/FROZEN
``` ```

View file

@ -14,7 +14,8 @@ CALIBRATION_SOLVER_PER_VIEW_EVIDENCE_V1=PASS/FROZEN
CALIBRATION_SOLVER_BUNDLE_REPAIR_V1=PASS/FROZEN CALIBRATION_SOLVER_BUNDLE_REPAIR_V1=PASS/FROZEN
CALIBRATION_WORKFLOW=IN_PROGRESS CALIBRATION_WORKFLOW=IN_PROGRESS
CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN
CURRENT_CALIBRATION_NEXT=WORKFLOW_EVIDENCE_MATERIALIZATION_V1 CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN
CURRENT_CALIBRATION_NEXT=WORKFLOW_SELECTED_EXECUTION_BINDING_V1
``` ```
## Authority ## Authority
@ -23,7 +24,7 @@ CURRENT_CALIBRATION_NEXT=WORKFLOW_EVIDENCE_MATERIALIZATION_V1
`docs/architecture/calibration_tooling.md` is the specialized Tooling authority. `docs/architecture/calibration_tooling.md` is the specialized Tooling authority.
`docs/architecture/calibration_workflow.md` is the specialized workflow authority. The public API remains `include/lardon3d/calibration_tooling.h`. `docs/architecture/calibration_workflow.md` is the specialized workflow authority. Its public API is `include/lardon3d/calibration_workflow.h`; the FROZEN Tooling API remains `include/lardon3d/calibration_tooling.h`.
A bounded corrective review established that Calibration Science v1 defines target planarity as a categorical physical attestation, not a numeric flatness tolerance. Tooling preserves its public structure layout while requiring `target_flatness_mm` to be NaN, so callers cannot invent a millimetre measurement. The canonical session's `planarity PASS <sha256>` evidence is bound through immutable initialization evidence. A bounded corrective review established that Calibration Science v1 defines target planarity as a categorical physical attestation, not a numeric flatness tolerance. Tooling preserves its public structure layout while requiring `target_flatness_mm` to be NaN, so callers cannot invent a millimetre measurement. The canonical session's `planarity PASS <sha256>` evidence is bound through immutable initialization evidence.
@ -57,7 +58,7 @@ dedicated physical calibration acquisition
-> real Sparse SfM -> real Sparse SfM
``` ```
The workflow coordinator is now implemented through its first bounded checkpoint. Input Boundary v1 validates immutable files, hashes, formats and complete optical-state equality without Project DB mutation. The current implementation gap is Evidence Materialization v1. It consumes the immutable `session.l3dcal` plus `detection.json`, `solve.json` and `evidence.json`, binds them to the exact selected execution and optical state, constructs the bounded Tooling evidence and never manufactures missing physical evidence. The workflow coordinator now has two PASS/FROZEN non-mutating checkpoints. Input Boundary v1 validates immutable files, hashes, formats and complete optical-state equality. Evidence Materialization v1 parses the retained session and solver bundle into bounded Science v1 target, per-view, coordinate, repeated-solve, fit, residual, hold-out and provenance evidence without opening Project DB. The current implementation gap is Selected Execution Binding v1: bind this external evidence to the exact selected execution, Capture optical assignments and campaign representation bytes, then construct the per-image Tooling entries. Missing or mismatched evidence is rejected; nothing is inferred.
## REQUIRED_PRODUCT_TARGET ## REQUIRED_PRODUCT_TARGET

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=1_CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION CURRENT_NEXT=1_CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING
``` ```
## Authority ## Authority
@ -20,7 +20,7 @@ Implementation remains unauthorized until the human explicitly authorizes a tran
Default dependency order: Default dependency order:
0. user-facing repository/UI language normalization where appropriate — PASS; 0. user-facing repository/UI language normalization where appropriate — PASS;
1. final usable calibration workflow — IN PROGRESS; Input Boundary v1 PASS/FROZEN; current next sub-boundary: Evidence Materialization v1; 1. final usable calibration workflow — IN PROGRESS; Input Boundary v1 and Evidence Materialization v1 PASS/FROZEN; current next sub-boundary: Selected Execution Binding v1;
2. dedicated physical calibrated real campaign; 2. dedicated physical calibrated real campaign;
3. real Sparse SfM proof; 3. real Sparse SfM proof;
4. durable Dense/OpenMVS orchestration; 4. durable Dense/OpenMVS orchestration;

View file

@ -0,0 +1,925 @@
#include <lardon3d/calibration_workflow.h>
#include <openssl/evp.h>
#include <algorithm>
#include <array>
#include <cerrno>
#include <charconv>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <fcntl.h>
#include <locale.h>
#include <map>
#include <set>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <unistd.h>
#include <utility>
#include <vector>
namespace {
constexpr size_t kShaBytes = 32;
constexpr size_t kMaxObservations =
LARDON3D_CALIBRATION_WORKFLOW_MAX_SELECTED_ITEMS * 48u;
struct ShaLess {
bool operator()(const std::array<unsigned char, 32>& a,
const std::array<unsigned char, 32>& b) const {
return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end());
}
};
bool hex_digit(char c, unsigned *value) {
if (c >= '0' && c <= '9') *value = static_cast<unsigned>(c - '0');
else if (c >= 'a' && c <= 'f') *value = static_cast<unsigned>(c - 'a' + 10);
else if (c >= 'A' && c <= 'F') *value = static_cast<unsigned>(c - 'A' + 10);
else return false;
return true;
}
bool parse_sha(std::string_view text, unsigned char output[32]) {
if (text.size() != 64) return false;
for (size_t i = 0; i < 32; ++i) {
unsigned hi = 0, lo = 0;
if (!hex_digit(text[2 * i], &hi) || !hex_digit(text[2 * i + 1], &lo))
return false;
output[i] = static_cast<unsigned char>((hi << 4u) | lo);
}
return true;
}
std::array<unsigned char, 32> sha_array(const unsigned char value[32]) {
std::array<unsigned char, 32> out{};
std::memcpy(out.data(), value, 32);
return out;
}
bool digest(const unsigned char *data, size_t size, unsigned char output[32]) {
unsigned int length = 0;
return EVP_Digest(data, size, output, &length, EVP_sha256(), nullptr) == 1 &&
length == 32;
}
Lardon3DCalibrationWorkflowResult read_verified(
const char *path, const unsigned char expected[32], std::string *output) {
if (!path || !expected || !output)
return LARDON3D_CALIBRATION_WORKFLOW_INVALID_ARGUMENT;
int fd = open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOFOLLOW);
if (fd < 0) {
if (errno == ELOOP) return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
struct stat st{};
if (fstat(fd, &st) != 0) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
if (!S_ISREG(st.st_mode)) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
}
if (st.st_size <= 0) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
}
if (static_cast<uint64_t>(st.st_size) >
LARDON3D_CALIBRATION_WORKFLOW_MAX_FILE_BYTES) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
}
const size_t size = static_cast<size_t>(st.st_size);
output->assign(size, '\0');
size_t used = 0;
while (used < size) {
ssize_t n = pread(fd, output->data() + used, size - used,
static_cast<off_t>(used));
if (n < 0 && errno == EINTR) continue;
if (n <= 0) {
close(fd);
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
used += static_cast<size_t>(n);
}
if (close(fd) != 0) return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
unsigned char actual[32]{};
if (!digest(reinterpret_cast<const unsigned char *>(output->data()),
output->size(), actual))
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
if (std::memcmp(actual, expected, 32) != 0)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
return LARDON3D_CALIBRATION_WORKFLOW_OK;
}
bool parse_u64(std::string_view text, uint64_t *value) {
if (!value || text.empty()) return false;
uint64_t v = 0;
auto result = std::from_chars(text.data(), text.data() + text.size(), v, 10);
if (result.ec != std::errc() || result.ptr != text.data() + text.size())
return false;
*value = v;
return true;
}
bool parse_u32(std::string_view text, uint32_t *value) {
uint64_t v = 0;
if (!parse_u64(text, &v) || v > UINT32_MAX) return false;
*value = static_cast<uint32_t>(v);
return true;
}
bool parse_double_c(std::string_view text, double *value) {
if (!value || text.empty() || text.size() > 128) return false;
char buffer[129];
std::memcpy(buffer, text.data(), text.size());
buffer[text.size()] = '\0';
locale_t locale = newlocale(LC_NUMERIC_MASK, "C", static_cast<locale_t>(0));
if (!locale) return false;
char *end = nullptr;
errno = 0;
double v = strtod_l(buffer, &end, locale);
freelocale(locale);
if (errno == ERANGE || end != buffer + text.size() || !std::isfinite(v))
return false;
*value = v;
return true;
}
std::vector<std::string_view> words(std::string_view line) {
std::vector<std::string_view> out;
size_t at = 0;
while (at < line.size()) {
while (at < line.size() && (line[at] == ' ' || line[at] == '\t')) ++at;
if (at == line.size()) break;
size_t end = at;
while (end < line.size() && line[end] != ' ' && line[end] != '\t') ++end;
out.push_back(line.substr(at, end - at));
at = end;
}
return out;
}
bool token_ok(std::string_view token) {
if (token.empty() ||
token.size() >= LARDON3D_CALIBRATION_WORKFLOW_OPTICAL_STATE_TOKEN_CAPACITY)
return false;
for (unsigned char c : token) {
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') || c == '_' || c == '-' || c == '.' ||
c == ':'))
return false;
}
return true;
}
struct SessionView {
std::array<unsigned char, 32> sha{};
uint32_t orientation = 0;
double pre_solve = -1.0;
double clipping = -1.0;
double distance = -1.0;
uint32_t distance_band = UINT32_MAX;
uint32_t coordinate_orientation = UINT32_MAX;
uint32_t coordinate_width = 0;
uint32_t coordinate_height = 0;
uint32_t coordinate_count = 0;
unsigned coordinate_coverage = 0;
std::vector<Lardon3DCalibrationToolingCoordinateCheck> checks;
};
struct SessionData {
std::string target_id;
std::string instrument;
std::string decoder;
std::string decoder_version;
std::string optical_token;
unsigned char generator_sha[32]{};
unsigned char planarity_sha[32]{};
unsigned char optical_sha[32]{};
double measurements[10]{};
double measurement_resolution = 0.0;
double white_border = 0.0;
std::vector<SessionView> views;
};
int coverage_bit(std::string_view label) {
static const std::array<std::string_view, 9> labels = {
"center", "top", "right", "bottom", "left",
"top_left", "top_right", "bottom_left", "bottom_right"};
for (size_t i = 0; i < labels.size(); ++i)
if (label == labels[i]) return static_cast<int>(i);
return -1;
}
bool parse_session_materialization(std::string_view text, SessionData *out) {
if (!out || text.empty() || text.back() != '\n' ||
text.find('\0') != std::string_view::npos ||
text.find('\r') != std::string_view::npos)
return false;
std::map<std::array<unsigned char, 32>, size_t, ShaLess> index;
bool target = false, measurement = false, border = false, planarity = false;
bool decoder = false, optical = false;
size_t at = 0, line_number = 0;
while (at < text.size()) {
size_t end = text.find('\n', at);
if (end == std::string_view::npos) return false;
std::string_view line = text.substr(at, end - at);
at = end + 1;
++line_number;
if (line_number == 1) {
if (line != "L3DCAL_SESSION_V1") return false;
continue;
}
if (line.empty()) continue;
auto w = words(line);
if (w.empty()) return false;
if (w[0] == "target") {
uint32_t sx = 0, sy = 0;
double square = 0.0, marker = 0.0;
if (target || w.size() != 8 || !token_ok(w[1]) ||
!parse_sha(w[2], out->generator_sha) ||
w[3] != "DICT_5X5_100" || !parse_u32(w[4], &sx) ||
!parse_u32(w[5], &sy) || !parse_double_c(w[6], &square) ||
!parse_double_c(w[7], &marker) || sx != 9 || sy != 7 ||
square != 30.0 || marker != 21.0)
return false;
out->target_id.assign(w[1]);
target = true;
} else if (w[0] == "measurement") {
if (measurement || w.size() != 13 || !token_ok(w[1]) ||
!parse_double_c(w[2], &out->measurement_resolution))
return false;
out->instrument.assign(w[1]);
for (size_t i = 0; i < 10; ++i)
if (!parse_double_c(w[i + 3], &out->measurements[i])) return false;
measurement = true;
} else if (w[0] == "white_border") {
if (border || w.size() != 2 || !parse_double_c(w[1], &out->white_border))
return false;
border = true;
} else if (w[0] == "planarity") {
if (planarity || w.size() != 3 || w[1] != "PASS" ||
!parse_sha(w[2], out->planarity_sha))
return false;
planarity = true;
} else if (w[0] == "decoder") {
if (decoder || w.size() != 3 || !token_ok(w[1]) || !token_ok(w[2]))
return false;
out->decoder.assign(w[1]);
out->decoder_version.assign(w[2]);
decoder = true;
} else if (w[0] == "optical_state") {
if (optical || w.size() != 3 || !parse_sha(w[1], out->optical_sha) ||
!token_ok(w[2]) || w[2] == "UNKNOWN")
return false;
out->optical_token.assign(w[2]);
optical = true;
} else if (w[0] == "image") {
if (w.size() != 4 || w[1].empty() || w[1].size() > 4096) return false;
SessionView view;
if (!parse_sha(w[2], view.sha.data()) || !parse_u32(w[3], &view.orientation) ||
(view.orientation != 0 && view.orientation != 90 &&
view.orientation != 180 && view.orientation != 270))
return false;
auto inserted = index.emplace(view.sha, out->views.size());
if (!inserted.second ||
out->views.size() >= LARDON3D_CALIBRATION_WORKFLOW_MAX_SELECTED_ITEMS)
return false;
out->views.push_back(view);
} else if (w[0] == "pre_solve" || w[0] == "clipping" ||
w[0] == "distance" || w[0] == "coordinate" ||
w[0] == "coordinate_point") {
if (w.size() < 2) return false;
std::array<unsigned char, 32> sha{};
if (!parse_sha(w[1], sha.data())) return false;
auto found = index.find(sha);
if (found == index.end()) return false;
SessionView& view = out->views[found->second];
if (w[0] == "pre_solve") {
if (w.size() != 3 || view.pre_solve >= 0 ||
!parse_double_c(w[2], &view.pre_solve))
return false;
} else if (w[0] == "clipping") {
if (w.size() != 3 || view.clipping >= 0 ||
!parse_double_c(w[2], &view.clipping))
return false;
} else if (w[0] == "distance") {
if (w.size() != 4 || view.distance > 0 ||
!parse_double_c(w[2], &view.distance) ||
!parse_u32(w[3], &view.distance_band) ||
view.distance <= 0 || view.distance_band > 2)
return false;
} else if (w[0] == "coordinate") {
double initial_dx = 0.0, initial_dy = 0.0;
if (w.size() != 10 || view.coordinate_count != 0 ||
w[2] != out->decoder || w[3] != out->decoder_version ||
!parse_u32(w[4], &view.coordinate_orientation) ||
!parse_u32(w[5], &view.coordinate_width) ||
!parse_u32(w[6], &view.coordinate_height) ||
!parse_u32(w[7], &view.coordinate_count) ||
!parse_double_c(w[8], &initial_dx) ||
!parse_double_c(w[9], &initial_dy) ||
view.coordinate_orientation != view.orientation ||
view.coordinate_width == 0 || view.coordinate_height == 0 ||
view.coordinate_count < 20 || view.coordinate_count > 48 ||
initial_dx < 0 || initial_dy < 0)
return false;
view.checks.reserve(view.coordinate_count);
} else {
double sx = 0.0, sy = 0.0, fx = 0.0, fy = 0.0;
if (w.size() != 7 || view.coordinate_count == 0 ||
view.checks.size() >= view.coordinate_count ||
!parse_double_c(w[3], &sx) || !parse_double_c(w[4], &sy) ||
!parse_double_c(w[5], &fx) || !parse_double_c(w[6], &fy))
return false;
int bit = coverage_bit(w[2]);
if (bit < 0) return false;
view.coordinate_coverage |= 1u << static_cast<unsigned>(bit);
Lardon3DCalibrationToolingCoordinateCheck check{};
std::memcpy(check.source_sha256, view.sha.data(), 32);
check.orientation_degrees = view.orientation;
check.dx_px = sx - fx;
check.dy_px = sy - fy;
view.checks.push_back(check);
}
} else {
return false;
}
}
if (!target || !measurement || !border || !planarity || !decoder || !optical ||
out->views.empty())
return false;
for (const SessionView& view : out->views) {
if (view.pre_solve < 0 || view.clipping < 0 || view.distance <= 0 ||
view.distance_band > 2 || view.coordinate_count < 20 ||
view.checks.size() != view.coordinate_count ||
view.coordinate_coverage != 0x1ffu)
return false;
}
return true;
}
class JsonCursor {
public:
explicit JsonCursor(std::string_view text) : text_(text) {}
void ws() {
while (at_ < text_.size() &&
(text_[at_] == ' ' || text_[at_] == '\n' ||
text_[at_] == '\r' || text_[at_] == '\t'))
++at_;
}
bool ch(char expected) {
ws();
if (at_ >= text_.size() || text_[at_] != expected) return false;
++at_;
return true;
}
bool key(std::string_view expected) {
std::string value;
return string(&value) && value == expected && ch(':');
}
bool string(std::string *output) {
ws();
if (at_ >= text_.size() || text_[at_] != '"') return false;
++at_;
output->clear();
while (at_ < text_.size()) {
unsigned char c = static_cast<unsigned char>(text_[at_++]);
if (c == '"') return true;
if (c < 0x20 || c == '\\') return false;
output->push_back(static_cast<char>(c));
}
return false;
}
bool u64(uint64_t *output) {
ws();
size_t start = at_;
if (start >= text_.size() || text_[start] < '0' || text_[start] > '9')
return false;
while (at_ < text_.size() && text_[at_] >= '0' && text_[at_] <= '9') ++at_;
return parse_u64(text_.substr(start, at_ - start), output);
}
bool u32(uint32_t *output) {
uint64_t v = 0;
if (!u64(&v) || v > UINT32_MAX) return false;
*output = static_cast<uint32_t>(v);
return true;
}
bool boolean(bool *output) {
ws();
if (text_.substr(at_, 4) == "true") {
at_ += 4; *output = true; return true;
}
if (text_.substr(at_, 5) == "false") {
at_ += 5; *output = false; return true;
}
return false;
}
bool hex_double(double *output) {
std::string value;
return string(&value) && parse_double_c(value, output);
}
bool done() {
ws();
return at_ == text_.size();
}
private:
std::string_view text_;
size_t at_ = 0;
};
bool comma(JsonCursor *j) { return j->ch(','); }
uint32_t rejection_reason(std::string_view reason) {
static const std::array<std::string_view, 10> reasons = {
"source_size", "decode", "decoded_dimensions", "insufficient_charuco",
"invalid_charuco_id", "occupancy", "target_physical_quadrants",
"clipping", "pre_solve_corner_rms", "coordinate_equivalence"};
for (size_t i = 0; i < reasons.size(); ++i)
if (reason == reasons[i]) return static_cast<uint32_t>(i + 1);
return 0;
}
struct DetectionView {
Lardon3DCalibrationToolingView tooling{};
uint32_t width = 0;
uint32_t height = 0;
bool coordinate_pass = false;
std::set<uint32_t> corner_ids;
};
bool parse_detection(std::string_view text, const SessionData& session,
std::vector<DetectionView> *views) {
JsonCursor j(text);
std::string format, decoder, decoder_version;
if (!j.ch('{') || !j.key("format") || !j.string(&format) ||
format != "L3DCAL_DETECTION_V1" || !comma(&j) ||
!j.key("decoder") || !j.string(&decoder) || decoder != session.decoder ||
!comma(&j) || !j.key("decoder_version") ||
!j.string(&decoder_version) || decoder_version != session.decoder_version ||
!comma(&j) || !j.key("views") || !j.ch('['))
return false;
std::map<std::array<unsigned char, 32>, size_t, ShaLess> session_index;
for (size_t i = 0; i < session.views.size(); ++i)
session_index.emplace(session.views[i].sha, i);
std::set<std::array<unsigned char, 32>, ShaLess> seen;
std::array<unsigned char, 32> previous{};
bool have_previous = false;
j.ws();
if (!j.ch(']')) {
while (true) {
DetectionView dv;
std::string sha_text, decision, reason;
uint32_t physical_quadrants = 0, comparison_points = 0;
double coordinate_dx = 0.0, coordinate_dy = 0.0;
bool coordinate_pass = false, holdout = false;
if (!j.ch('{') || !j.key("source_sha256") || !j.string(&sha_text) ||
!parse_sha(sha_text, dv.tooling.source_sha256) || !comma(&j) ||
!j.key("orientation") || !j.u32(&dv.tooling.orientation_degrees) ||
!comma(&j) || !j.key("oriented_width") || !j.u32(&dv.width) ||
!comma(&j) || !j.key("oriented_height") || !j.u32(&dv.height) ||
!comma(&j) || !j.key("decision") || !j.string(&decision) ||
!comma(&j) || !j.key("reason") || !j.string(&reason) ||
!comma(&j) || !j.key("frame_region") || !j.u32(&dv.tooling.quadrant) ||
!comma(&j) || !j.key("distance_band") ||
!j.u32(&dv.tooling.distance_band) || !comma(&j) ||
!j.key("holdout") || !j.boolean(&holdout) || !comma(&j) ||
!j.key("target_occupancy") || !j.hex_double(&dv.tooling.target_occupancy) ||
!comma(&j) || !j.key("normal_angle_degrees") ||
!j.hex_double(&dv.tooling.normal_angle_degrees) || !comma(&j) ||
!j.key("measured_distance_metres") ||
!j.hex_double(&dv.tooling.distance_metres) || !comma(&j) ||
!j.key("pre_solve_corner_rms_px") ||
!j.hex_double(&dv.tooling.corner_rms_px) || !comma(&j) ||
!j.key("clipping_fraction") ||
!j.hex_double(&dv.tooling.clipped_fraction) || !comma(&j) ||
!j.key("physical_target_quadrants") || !j.u32(&physical_quadrants) ||
!comma(&j) || !j.key("target_corner_quadrant_mask") ||
!j.u32(&dv.tooling.target_corner_quadrant_mask) || !comma(&j) ||
!j.key("corner_count") || !j.u32(&dv.tooling.corner_count) ||
!comma(&j) || !j.key("residual_count") ||
!j.u32(&dv.tooling.residual_count) || !comma(&j) ||
!j.key("high_residual_count") ||
!j.u32(&dv.tooling.high_residual_count) || !comma(&j) ||
!j.key("reprojection_rmse_px") ||
!j.hex_double(&dv.tooling.reprojection_rmse_px) || !comma(&j) ||
!j.key("maximum_residual_px") ||
!j.hex_double(&dv.tooling.maximum_residual_px) || !comma(&j) ||
!j.key("coordinate_equivalence") || !j.ch('{') ||
!j.key("comparison_points") || !j.u32(&comparison_points) ||
!comma(&j) || !j.key("max_abs_dx_px") || !j.hex_double(&coordinate_dx) ||
!comma(&j) || !j.key("max_abs_dy_px") || !j.hex_double(&coordinate_dy) ||
!comma(&j) || !j.key("pass") || !j.boolean(&coordinate_pass) ||
!j.ch('}') || !comma(&j) || !j.key("corners") || !j.ch('['))
return false;
auto key = sha_array(dv.tooling.source_sha256);
auto sit = session_index.find(key);
if (sit == session_index.end() || !seen.insert(key).second) return false;
if (have_previous &&
!std::lexicographical_compare(previous.begin(), previous.end(),
key.begin(), key.end()))
return false;
previous = key;
have_previous = true;
const SessionView& sv = session.views[sit->second];
size_t corner_count = 0;
j.ws();
if (!j.ch(']')) {
while (true) {
uint32_t id = 0;
double x = 0.0, y = 0.0;
if (!j.ch('{') || !j.key("id") || !j.u32(&id) || !comma(&j) ||
!j.key("x") || !j.hex_double(&x) || !comma(&j) ||
!j.key("y") || !j.hex_double(&y) || !j.ch('}') ||
!dv.corner_ids.insert(id).second)
return false;
++corner_count;
j.ws();
if (j.ch(']')) break;
if (!comma(&j)) return false;
}
}
if (!j.ch('}')) return false;
dv.tooling.accepted = decision == "accepted" ? 1u : 0u;
if (decision != "accepted" && decision != "rejected") return false;
if (dv.tooling.accepted) {
if (reason != "-" || !coordinate_pass || dv.width == 0 || dv.height == 0 ||
dv.tooling.orientation_degrees != sv.orientation ||
dv.tooling.corner_rms_px != sv.pre_solve ||
dv.tooling.distance_metres != sv.distance ||
dv.tooling.distance_band != sv.distance_band ||
comparison_points != sv.coordinate_count ||
dv.tooling.corner_count != corner_count ||
dv.tooling.residual_count != dv.tooling.corner_count)
return false;
} else {
dv.tooling.rejection_reason = rejection_reason(reason);
if (reason == "-" || dv.tooling.rejection_reason == 0 || holdout)
return false;
}
dv.tooling.holdout = holdout ? 1u : 0u;
unsigned pop = 0;
for (unsigned bit = 0; bit < 4; ++bit)
pop += (dv.tooling.target_corner_quadrant_mask >> bit) & 1u;
if (physical_quadrants != pop ||
dv.tooling.high_residual_count > dv.tooling.residual_count ||
coordinate_dx < 0 || coordinate_dy < 0)
return false;
dv.coordinate_pass = coordinate_pass;
views->push_back(std::move(dv));
j.ws();
if (j.ch(']')) break;
if (!comma(&j)) return false;
}
}
if (!j.ch('}') || !j.done() || views->size() != session.views.size())
return false;
return true;
}
bool parse_parameter_array(JsonCursor *j, double output[8]) {
if (!j->ch('[')) return false;
for (size_t i = 0; i < 8; ++i) {
if (!j->hex_double(&output[i])) return false;
if (i + 1 != 8 && !comma(j)) return false;
}
return j->ch(']');
}
bool parse_vec3(JsonCursor *j) {
if (!j->ch('[')) return false;
for (size_t i = 0; i < 3; ++i) {
double value = 0.0;
if (!j->hex_double(&value)) return false;
if (i + 1 != 3 && !comma(j)) return false;
}
return j->ch(']');
}
struct SolveData {
double repeated[3][8]{};
double fit[8]{};
};
bool parse_solve(std::string_view text, size_t accepted_count, SolveData *out) {
JsonCursor j(text);
std::string format;
if (!j.ch('{') || !j.key("format") || !j.string(&format) ||
format != "L3DCAL_SOLVE_V1" || !comma(&j) ||
!j.key("runs") || !j.ch('['))
return false;
for (uint32_t run = 0; run < 3; ++run) {
uint32_t run_id = UINT32_MAX;
double opencv_rms = 0.0;
if (!j.ch('{') || !j.key("run") || !j.u32(&run_id) || run_id != run ||
!comma(&j) || !j.key("params") ||
!parse_parameter_array(&j, out->repeated[run]) || !comma(&j) ||
!j.key("opencv_rms_px") || !j.hex_double(&opencv_rms) ||
opencv_rms < 0 || !comma(&j) || !j.key("poses") || !j.ch('['))
return false;
size_t poses = 0;
j.ws();
if (!j.ch(']')) {
while (true) {
if (!j.ch('{') || !j.key("rvec") || !parse_vec3(&j) ||
!comma(&j) || !j.key("tvec_m") || !parse_vec3(&j) || !j.ch('}'))
return false;
++poses;
j.ws();
if (j.ch(']')) break;
if (!comma(&j)) return false;
}
}
if (poses != accepted_count || !j.ch('}')) return false;
if (run != 2 && !comma(&j)) return false;
}
if (!j.ch(']') || !comma(&j) || !j.key("fit_params") ||
!parse_parameter_array(&j, out->fit) || !j.ch('}') || !j.done())
return false;
for (size_t run = 1; run < 3; ++run)
if (std::memcmp(out->repeated[0], out->repeated[run],
sizeof(out->repeated[0])) != 0)
return false;
return true;
}
struct EvidenceData {
double global_rmse = 0.0;
double global_max = 0.0;
double high_fraction = 0.0;
double holdout_rmse = 0.0;
double holdout_max = 0.0;
double maximum_delta = 0.0;
uint32_t flags = 0;
};
bool parse_flags(std::string_view text, uint32_t *flags) {
if (text.size() != 3 || text[0] != '0' || text[1] != 'x') return false;
unsigned v = 0;
if (!hex_digit(text[2], &v) || v > UINT32_MAX) return false;
*flags = static_cast<uint32_t>(v);
return true;
}
bool parse_evidence(std::string_view text, const SessionData& session,
const std::vector<DetectionView>& views,
EvidenceData *out) {
JsonCursor j(text);
std::string format, target_id, generator, instrument, planarity, optical,
optical_state, flags_text;
double resolution = 0.0;
bool deterministic = false;
if (!j.ch('{') || !j.key("format") || !j.string(&format) ||
format != "L3DCAL_EVIDENCE_BUNDLE_V1" || !comma(&j) ||
!j.key("target") || !j.ch('{') ||
!j.key("id") || !j.string(&target_id) || !comma(&j) ||
!j.key("generator_sha256") || !j.string(&generator) || !comma(&j) ||
!j.key("instrument") || !j.string(&instrument) || !comma(&j) ||
!j.key("resolution_mm") || !j.hex_double(&resolution) || !comma(&j) ||
!j.key("planarity_evidence_sha256") || !j.string(&planarity) ||
!j.ch('}') || !comma(&j) ||
!j.key("optical_sha256") || !j.string(&optical) || !comma(&j) ||
!j.key("optical_state") || !j.string(&optical_state) || !comma(&j) ||
!j.key("validation_flags") || !j.string(&flags_text) || !comma(&j) ||
!j.key("global_rmse_px") || !j.hex_double(&out->global_rmse) ||
!comma(&j) || !j.key("maximum_residual_px") ||
!j.hex_double(&out->global_max) || !comma(&j) ||
!j.key("high_residual_fraction") || !j.hex_double(&out->high_fraction) ||
!comma(&j) || !j.key("holdout") || !j.ch('{') ||
!j.key("rmse_px") || !j.hex_double(&out->holdout_rmse) || !comma(&j) ||
!j.key("maximum_px") || !j.hex_double(&out->holdout_max) ||
!j.ch('}') || !comma(&j) ||
!j.key("maximum_parameter_delta_px") ||
!j.hex_double(&out->maximum_delta) || !comma(&j) ||
!j.key("deterministic_full_solve_equality") ||
!j.boolean(&deterministic) || !comma(&j) ||
!j.key("residuals") || !j.ch('['))
return false;
unsigned char generator_sha[32]{}, planarity_sha[32]{}, optical_sha[32]{};
if (target_id != session.target_id || instrument != session.instrument ||
resolution != session.measurement_resolution ||
!parse_sha(generator, generator_sha) ||
!parse_sha(planarity, planarity_sha) || !parse_sha(optical, optical_sha) ||
std::memcmp(generator_sha, session.generator_sha, 32) != 0 ||
std::memcmp(planarity_sha, session.planarity_sha, 32) != 0 ||
std::memcmp(optical_sha, session.optical_sha, 32) != 0 ||
optical_state != session.optical_token ||
!parse_flags(flags_text, &out->flags) || out->flags != 0x0fu ||
!deterministic || out->global_rmse < 0 || out->global_max < 0 ||
out->high_fraction < 0 || out->high_fraction > 1 ||
out->holdout_rmse < 0 || out->holdout_max < 0 ||
out->maximum_delta < 0)
return false;
std::map<std::array<unsigned char, 32>, size_t, ShaLess> view_index;
for (size_t i = 0; i < views.size(); ++i)
view_index.emplace(sha_array(views[i].tooling.source_sha256), i);
std::vector<uint32_t> counts(views.size(), 0), highs(views.size(), 0);
std::set<std::pair<size_t, uint32_t>> residual_ids;
uint64_t total = 0, total_high = 0;
j.ws();
if (!j.ch(']')) {
while (true) {
std::string sha_text;
uint32_t corner_id = 0;
double dx = 0.0, dy = 0.0, rmse = 0.0;
if (!j.ch('{') || !j.key("source_sha256") || !j.string(&sha_text) ||
!comma(&j) || !j.key("corner_id") || !j.u32(&corner_id) ||
!comma(&j) || !j.key("dx_px") || !j.hex_double(&dx) ||
!comma(&j) || !j.key("dy_px") || !j.hex_double(&dy) ||
!comma(&j) || !j.key("rmse_px") || !j.hex_double(&rmse) ||
!j.ch('}'))
return false;
unsigned char sha[32]{};
if (!parse_sha(sha_text, sha)) return false;
auto found = view_index.find(sha_array(sha));
if (found == view_index.end()) return false;
size_t vi = found->second;
const DetectionView& dv = views[vi];
if (!dv.tooling.accepted || dv.corner_ids.find(corner_id) == dv.corner_ids.end() ||
!residual_ids.emplace(vi, corner_id).second ||
rmse != dv.tooling.reprojection_rmse_px)
return false;
++counts[vi];
++total;
const double squared = dx * dx + dy * dy;
if (!std::isfinite(squared)) return false;
if (squared > 1.0) {
++highs[vi];
++total_high;
}
if (total > kMaxObservations) return false;
j.ws();
if (j.ch(']')) break;
if (!comma(&j)) return false;
}
}
if (!j.ch('}') || !j.done() || total == 0) return false;
for (size_t i = 0; i < views.size(); ++i) {
if (views[i].tooling.accepted) {
if (counts[i] != views[i].tooling.residual_count ||
highs[i] != views[i].tooling.high_residual_count)
return false;
} else if (counts[i] != 0) {
return false;
}
}
const double calculated_fraction =
static_cast<double>(total_high) / static_cast<double>(total);
if (calculated_fraction != out->high_fraction) return false;
return true;
}
bool validation_binding(const Lardon3DCalibrationWorkflowInputBoundary& boundary,
unsigned char output[32]) {
static const char domain[] = "L3DCAL_WORKFLOW_VALIDATION_V1\n";
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) return false;
bool ok = EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr) == 1 &&
EVP_DigestUpdate(ctx, domain, sizeof(domain) - 1) == 1 &&
EVP_DigestUpdate(ctx, boundary.detection_sha256, 32) == 1 &&
EVP_DigestUpdate(ctx, boundary.solve_sha256, 32) == 1 &&
EVP_DigestUpdate(ctx, boundary.evidence_sha256, 32) == 1 &&
EVP_DigestUpdate(ctx, boundary.producer_sha256, 32) == 1;
unsigned int length = 0;
ok = ok && EVP_DigestFinal_ex(ctx, output, &length) == 1 && length == 32;
EVP_MD_CTX_free(ctx);
return ok;
}
} // namespace
extern "C" Lardon3DCalibrationWorkflowResult
lardon3d_calibration_workflow_materialize_external_evidence(
const Lardon3DCalibrationWorkflowInputFiles *files,
Lardon3DCalibrationToolingView *views, size_t view_capacity,
Lardon3DCalibrationToolingCoordinateCheck *coordinate_checks,
size_t coordinate_check_capacity,
Lardon3DCalibrationWorkflowExternalEvidence *output) {
if (!files || !views || !coordinate_checks || !output)
return LARDON3D_CALIBRATION_WORKFLOW_INVALID_ARGUMENT;
std::memset(output, 0, sizeof(*output));
Lardon3DCalibrationWorkflowInputBoundary boundary{};
Lardon3DCalibrationWorkflowResult result =
lardon3d_calibration_workflow_validate_input_boundary(files, &boundary);
if (result != LARDON3D_CALIBRATION_WORKFLOW_OK) return result;
std::string session_text, detection_text, solve_text, evidence_text;
const struct {
const char *path;
const unsigned char *sha;
std::string *text;
} inputs[] = {
{files->session_path, boundary.session_sha256, &session_text},
{files->detection_path, boundary.detection_sha256, &detection_text},
{files->solve_path, boundary.solve_sha256, &solve_text},
{files->evidence_path, boundary.evidence_sha256, &evidence_text},
};
for (const auto& input : inputs) {
result = read_verified(input.path, input.sha, input.text);
if (result != LARDON3D_CALIBRATION_WORKFLOW_OK) return result;
}
SessionData session;
if (!parse_session_materialization(session_text, &session))
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
if (std::memcmp(session.optical_sha, boundary.optical_state_sha256, 32) != 0 ||
session.optical_token != boundary.optical_state_token)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
std::vector<DetectionView> parsed_views;
if (!parse_detection(detection_text, session, &parsed_views))
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
size_t accepted = 0;
uint64_t support_observations = 0;
for (const DetectionView& view : parsed_views) {
if (view.tooling.accepted) {
++accepted;
support_observations += view.tooling.residual_count;
if (support_observations > UINT32_MAX)
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
}
}
SolveData solve;
if (!parse_solve(solve_text, accepted, &solve))
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
EvidenceData evidence;
if (!parse_evidence(evidence_text, session, parsed_views, &evidence))
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
size_t check_count = 0;
for (const SessionView& view : session.views) {
if (SIZE_MAX - check_count < view.checks.size())
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
check_count += view.checks.size();
}
if (parsed_views.size() > view_capacity ||
check_count > coordinate_check_capacity)
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
for (size_t i = 0; i < parsed_views.size(); ++i)
views[i] = parsed_views[i].tooling;
size_t at = 0;
for (const SessionView& view : session.views)
for (const auto& check : view.checks) coordinate_checks[at++] = check;
output->boundary = boundary;
std::memcpy(output->target_sha256, session.generator_sha, 32);
std::memcpy(output->optical_state_sha256, boundary.optical_state_sha256, 32);
std::memcpy(output->solver_executable_sha256,
boundary.solver_executable_sha256, 32);
std::memcpy(output->solver_configuration_sha256,
boundary.solver_configuration_sha256, 32);
std::memcpy(output->initialization_evidence_sha256,
boundary.session_sha256, 32);
if (!validation_binding(boundary, output->validation_evidence_sha256))
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
output->target_family =
LARDON3D_CALIBRATION_TOOLING_TARGET_CHARUCO_9X7_DICT_5X5_100;
output->target_squares_x = 9;
output->target_squares_y = 7;
output->target_square_length_mm = 30.0;
output->target_marker_length_mm = 21.0;
output->target_active_width_mm = 270.0;
output->target_active_height_mm = 210.0;
output->target_white_border_mm = session.white_border;
for (size_t i = 0; i < 10; ++i)
output->target_measurements_mm[i] = session.measurements[i];
output->measurement_resolution_mm = session.measurement_resolution;
output->target_flatness_mm = NAN;
output->holdout_rmse_px = evidence.holdout_rmse;
output->holdout_maximum_residual_px = evidence.holdout_max;
output->extra_distortion_coefficient_count = 0;
output->views = views;
output->view_count = parsed_views.size();
output->coordinate_checks = coordinate_checks;
output->coordinate_check_count = check_count;
std::memcpy(output->repeated_parameters, solve.repeated,
sizeof(output->repeated_parameters));
std::memcpy(output->fit_parameters, solve.fit,
sizeof(output->fit_parameters));
output->support_images = static_cast<uint32_t>(accepted);
output->support_observations = static_cast<uint32_t>(support_observations);
output->reprojection_rmse_px = evidence.global_rmse;
output->maximum_residual_px = evidence.global_max;
output->high_residual_fraction = evidence.high_fraction;
output->maximum_parameter_delta = evidence.maximum_delta;
output->validation_flags = evidence.flags;
return LARDON3D_CALIBRATION_WORKFLOW_OK;
}

View file

@ -0,0 +1,267 @@
#include <lardon3d/calibration_workflow.h>
#include <openssl/evp.h>
#include <array>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
#include <unistd.h>
#define CHECK(x) do { if(!(x)){ std::fprintf(stderr,"materialize failure %d: %s\n",__LINE__,#x); return false; } } while(0)
namespace {
std::string hex_repeat(char c) { return std::string(64, c); }
std::string hf(double value) {
std::ostringstream o;
o.imbue(std::locale::classic());
o << std::hexfloat << value;
return o.str();
}
bool write_text(const std::filesystem::path& path, const std::string& text) {
std::ofstream out(path, std::ios::binary | std::ios::trunc);
out << text;
return static_cast<bool>(out);
}
bool sha_file(const std::filesystem::path& path, unsigned char out[32]) {
std::ifstream in(path, std::ios::binary);
std::ostringstream bytes;
bytes << in.rdbuf();
const std::string data = bytes.str();
unsigned int n = 0;
return in && EVP_Digest(data.data(), data.size(), out, &n, EVP_sha256(), nullptr) == 1 &&
n == 32;
}
std::string sha_file_hex(const std::filesystem::path& path) {
unsigned char value[32]{};
if (!sha_file(path, value)) return {};
static const char digits[] = "0123456789abcdef";
std::string out(64, '0');
for (size_t i = 0; i < 32; ++i) {
out[2 * i] = digits[value[i] >> 4];
out[2 * i + 1] = digits[value[i] & 15];
}
return out;
}
struct Fixture {
std::filesystem::path root, session, detection, solve, evidence, producer, campaign;
Lardon3DCalibrationWorkflowInputFiles files{};
};
std::string coordinate_lines(const std::string& sha, double distance, unsigned band) {
static const std::array<const char*, 9> labels = {
"center","top","right","bottom","left","top_left","top_right","bottom_left","bottom_right"};
std::ostringstream s;
s << "pre_solve " << sha << " 0.1\n"
<< "clipping " << sha << " 0\n"
<< "coordinate " << sha << " qualified_decoder 1 0 1000 800 20 0 0\n";
for (int i = 0; i < 20; ++i)
s << "coordinate_point " << sha << " " << labels[static_cast<size_t>(i)%labels.size()]
<< " " << i << " " << i+1 << " " << i << " " << i+1 << "\n";
s << "distance " << sha << " " << std::setprecision(17)
<< std::defaultfloat << distance << " " << band << "\n";
return s.str();
}
std::string detection_view(const std::string& sha, unsigned region, unsigned band,
double distance, bool last) {
std::ostringstream s;
s << "{\"source_sha256\":\"" << sha
<< "\",\"orientation\":0,\"oriented_width\":1000,\"oriented_height\":800"
<< ",\"decision\":\"accepted\",\"reason\":\"-\""
<< ",\"frame_region\":" << region
<< ",\"distance_band\":" << band
<< ",\"holdout\":false"
<< ",\"target_occupancy\":\"" << hf(.3)
<< "\",\"normal_angle_degrees\":\"" << hf(25.0)
<< "\",\"measured_distance_metres\":\"" << hf(distance)
<< "\",\"pre_solve_corner_rms_px\":\"" << hf(.1)
<< "\",\"clipping_fraction\":\"" << hf(0.0)
<< "\",\"physical_target_quadrants\":3"
<< ",\"target_corner_quadrant_mask\":7"
<< ",\"corner_count\":20,\"residual_count\":20,\"high_residual_count\":0"
<< ",\"reprojection_rmse_px\":\"" << hf(.1)
<< "\",\"maximum_residual_px\":\"" << hf(.2)
<< "\",\"coordinate_equivalence\":{\"comparison_points\":20"
<< ",\"max_abs_dx_px\":\"" << hf(0.0)
<< "\",\"max_abs_dy_px\":\"" << hf(0.0)
<< "\",\"pass\":true},\"corners\":[";
for (int i = 0; i < 20; ++i) {
if (i) s << ",";
s << "{\"id\":" << i << ",\"x\":\"" << hf(100.0+i)
<< "\",\"y\":\"" << hf(200.0+i) << "\"}";
}
s << "]}" << (last ? "\n" : " ,\n");
return s.str();
}
std::string params(double first = 1000.0) {
const double p[8] = {first,1001,500,400,.01,-.01,.001,-.001};
std::ostringstream s;
s << "[";
for (int i = 0; i < 8; ++i) {
if (i) s << ",";
s << "\"" << hf(p[i]) << "\"";
}
s << "]";
return s.str();
}
std::string pose() {
return "{\"rvec\":[\"" + hf(0) + "\",\"" + hf(0) + "\",\"" + hf(0) +
"\"],\"tvec_m\":[\"" + hf(0) + "\",\"" + hf(0) + "\",\"" + hf(1) + "\"]}";
}
std::string solve_text(bool mismatch) {
std::ostringstream s;
s << "{\n\"format\":\"L3DCAL_SOLVE_V1\",\n\"runs\":[\n";
for (int run = 0; run < 3; ++run) {
s << "{\"run\":" << run << ",\"params\":" << params(mismatch && run==1 ? 999.0 : 1000.0)
<< ",\"opencv_rms_px\":\"" << hf(.1) << "\",\"poses\":["
<< pose() << "," << pose() << "]}" << (run==2 ? "\n" : " ,\n");
}
s << "],\n\"fit_params\":" << params() << "\n}\n";
return s.str();
}
std::string evidence_text(const std::string& optical,
const std::string& generator,
const std::string& planarity,
const std::string& a,
const std::string& b) {
std::ostringstream s;
s << "{\n\"format\":\"L3DCAL_EVIDENCE_BUNDLE_V1\",\n"
<< "\"target\":{\"id\":\"board\",\"generator_sha256\":\"" << generator
<< "\",\"instrument\":\"caliper\",\"resolution_mm\":\"" << hf(.1)
<< "\",\"planarity_evidence_sha256\":\"" << planarity << "\"},\n"
<< "\"optical_sha256\":\"" << optical << "\",\n"
<< "\"optical_state\":\"body_objective_zoom_focus_stabilization_format_pipeline\",\n"
<< "\"validation_flags\":\"0xf\",\n"
<< "\"global_rmse_px\":\"" << hf(.1)
<< "\",\"maximum_residual_px\":\"" << hf(.2)
<< "\",\"high_residual_fraction\":\"" << hf(0.0) << "\",\n"
<< "\"holdout\":{\"rmse_px\":\"" << hf(.1)
<< "\",\"maximum_px\":\"" << hf(.2) << "\"},\n"
<< "\"maximum_parameter_delta_px\":\"" << hf(0.0) << "\",\n"
<< "\"deterministic_full_solve_equality\":true,\n\"residuals\":[";
bool first = true;
for (const std::string* sha : {&a, &b}) {
for (int i = 0; i < 20; ++i) {
if (!first) s << ",";
first = false;
s << "{\"source_sha256\":\"" << *sha << "\",\"corner_id\":" << i
<< ",\"dx_px\":\"" << hf(.1) << "\",\"dy_px\":\"" << hf(0.0)
<< "\",\"rmse_px\":\"" << hf(.1) << "\"}";
}
}
s << "]\n}\n";
return s.str();
}
bool make_fixture(Fixture *f, bool mismatch_solve = false) {
char temp[]="/tmp/lardon3d-materialize-XXXXXX";
char *r=mkdtemp(temp); if(!r) return false;
f->root=r; f->session=f->root/"session"; f->detection=f->root/"detection";
f->solve=f->root/"solve"; f->evidence=f->root/"evidence";
f->producer=f->root/"producer"; f->campaign=f->root/"campaign";
const std::string optical=hex_repeat('1'), generator=hex_repeat('2'),
planarity=hex_repeat('3'), a=hex_repeat('4'), b=hex_repeat('5');
std::ostringstream session;
session << "L3DCAL_SESSION_V1\n"
<< "target board " << generator << " DICT_5X5_100 9 7 30 21\n"
<< "measurement caliper 0.1 30 30 30 30 30 30 30 30 30 30\n"
<< "white_border 30\n"
<< "planarity PASS " << planarity << "\n"
<< "decoder qualified_decoder 1\n"
<< "optical_state " << optical << " body_objective_zoom_focus_stabilization_format_pipeline\n"
<< "image /a " << a << " 0\n"
<< "image /b " << b << " 0\n"
<< coordinate_lines(a,.3,0) << coordinate_lines(b,.5,1);
if(!write_text(f->session,session.str())) return false;
if(!write_text(f->detection,
"{\n\"format\":\"L3DCAL_DETECTION_V1\",\n\"decoder\":\"qualified_decoder\",\n"
"\"decoder_version\":\"1\",\n\"views\":[\n" +
detection_view(a,0,0,.3,false)+detection_view(b,1,1,.5,true)+"]\n}\n")) return false;
if(!write_text(f->solve,solve_text(mismatch_solve))) return false;
if(!write_text(f->evidence,evidence_text(optical,generator,planarity,a,b))) return false;
const std::string session_sha = sha_file_hex(f->session);
if (session_sha.empty()) return false;
if(!write_text(f->producer,
"{\n\"format\":\"L3DCAL_PRODUCER_V1\",\n"
"\"solver_executable_sha256\":\"" + hex_repeat('a') + "\",\n"
"\"solver_configuration_sha256\":\"" + hex_repeat('b') + "\",\n"
"\"session_sha256\":\"" + session_sha + "\",\n"
"\"opencv_version\":\"5.0.0\",\n"
"\"opencv_build_sha256\":\"" + hex_repeat('c') + "\",\n"
"\"threads\":1,\n\"rng_seed\":1278432342,\n"
"\"optical_sha256\":\"" + optical + "\"\n}\n")) return false;
if(!write_text(f->campaign,
"L3DCAL_CAMPAIGN_STATE_V1\n"
"execution 42\n"
"optical_configuration 7\n"
"optical_state " + optical +
" body_objective_zoom_focus_stabilization_format_pipeline\n"
"capture 0 101\ncapture 1 102\n")) return false;
f->files={f->session.c_str(),f->detection.c_str(),f->solve.c_str(),
f->evidence.c_str(),f->producer.c_str(),f->campaign.c_str()};
return true;
}
bool valid() {
Fixture f; CHECK(make_fixture(&f));
Lardon3DCalibrationToolingView views[2]{};
Lardon3DCalibrationToolingCoordinateCheck checks[40]{};
Lardon3DCalibrationWorkflowExternalEvidence out{};
CHECK(lardon3d_calibration_workflow_materialize_external_evidence(
&f.files,views,2,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_OK);
CHECK(out.view_count==2 && out.coordinate_check_count==40);
CHECK(out.support_images==2 && out.support_observations==40);
CHECK(out.validation_flags==15 && out.target_white_border_mm==30.0);
CHECK(out.repeated_parameters[0][0]==1000.0 && out.fit_parameters[0]==1000.0);
CHECK(views[0].accepted==1 && views[0].corner_count==20 &&
views[1].distance_band==1);
CHECK(checks[0].dx_px==0.0 && checks[0].dy_px==0.0);
unsigned char session_sha[32]{};
CHECK(sha_file(f.session, session_sha));
CHECK(std::memcmp(out.initialization_evidence_sha256, session_sha, 32) == 0);
std::filesystem::remove_all(f.root);
return true;
}
bool capacity_and_mismatch() {
Fixture f; CHECK(make_fixture(&f));
Lardon3DCalibrationToolingView views[2]{};
Lardon3DCalibrationToolingCoordinateCheck checks[40]{};
Lardon3DCalibrationWorkflowExternalEvidence out{};
CHECK(lardon3d_calibration_workflow_materialize_external_evidence(
&f.files,views,1,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_CAPACITY);
std::filesystem::remove_all(f.root);
CHECK(make_fixture(&f,true));
CHECK(lardon3d_calibration_workflow_materialize_external_evidence(
&f.files,views,2,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
std::filesystem::remove_all(f.root);
return true;
}
} // namespace
int main(){
if(!valid()||!capacity_and_mismatch()) return 1;
std::puts("CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS");
return 0;
}