feat: bind calibration workflow selected execution

This commit is contained in:
fy59 2026-09-03 10:39:52 +02:00
parent 6a670fd9e9
commit df7b589174
11 changed files with 868 additions and 28 deletions

View file

@ -6,7 +6,8 @@
CALIBRATION_WORKFLOW=IN_PROGRESS CALIBRATION_WORKFLOW=IN_PROGRESS
CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN
CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN
CURRENT_WORKFLOW_NEXT=SELECTED_EXECUTION_BINDING_V1 CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS/FROZEN
CURRENT_WORKFLOW_NEXT=TOOLING_BOOTSTRAP_READY
``` ```
## Authority ## Authority
@ -154,28 +155,47 @@ explicit Project DB optical configuration.
Absence or disagreement remains `CALIBRATION_UNAVAILABLE`. Absence or disagreement remains `CALIBRATION_UNAVAILABLE`.
## Current next boundary ## Selected Execution Binding v1
```text ```text
CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1 CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS/FROZEN
``` ```
The next stage may read Project DB, but must not mutate it. It must: The implementation is additive:
- load the exact selected execution and require the calibration/ready stage ```text
appropriate to the existing FROZEN contract; src/calibration_workflow_bind.cpp
- require exact selected item count and order; tests/test_calibration_workflow_bind.cpp
- match every campaign-state Capture row to the selected item; ```
- load each Capture's explicit v23 optical assignment and require the exact
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.
Only after that read-only binding passes may a final workflow boundary invoke This boundary is read-only. It may read Project DB and managed representation
the FROZEN Tooling/Bootstrap path and transition the selected execution to bytes, but does not attach a calibration scope, invoke Calibration Tooling or
truthful `READY`. Bootstrap, or transition the selected execution to `READY`. It proves:
- exact selected-execution stage, completion, item order and Capture mapping;
- exact explicit v23 optical configuration, including campaign-origin facts
where present;
- exact selected-image/Capture relation and READY image asset identity;
- managed representation size and SHA-256 through project-relative `openat`
descent that rejects absolute paths, dot components, symlinks, non-directory
components and non-regular final files;
- grayscale OpenCV decoded width/height equal to the accepted materialized
calibration geometry; and
- deterministic selected-item-order `Lardon3DCalibrationToolingEntry`
construction from the published solve values, without averaging or solver
recomputation.
Entries are staged internally and published to caller storage only after every
selected item passes. Exact retries are read-only and deterministic.
## Current next boundary
The final calibration workflow boundary is:
```text
validated input -> materialized evidence -> selected-execution binding
-> FROZEN Calibration Tooling -> FROZEN Calibration Bootstrap -> READY
```
It must preserve the binding's exact provenance and use only the FROZEN
Tooling/Bootstrap import path. No failure before import may mutate Project DB.

View file

@ -24,7 +24,7 @@ SOURCE_COMMENT_AUDIT PASS
PRODUCT_DEFINITION PASS/FROZEN PRODUCT_DEFINITION PASS/FROZEN
PROMPT_TREE CURRENT PROMPT_TREE CURRENT
USER_FACING_UI_LANGUAGE_NORMALIZATION PASS USER_FACING_UI_LANGUAGE_NORMALIZATION PASS
CURRENT_NEXT CALIBRATION_WORKFLOW_COORDINATOR CURRENT_NEXT CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY
``` ```
The current Project DB head is additive: The current Project DB head is additive:
@ -531,6 +531,8 @@ The solver session contract now also requires explicit measured `white_border >=
`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. `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.
`CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS/FROZEN`: a read-only coordinator boundary now proves the exact selected execution and Capture mapping, explicit optical configuration, managed representation size/SHA-256, safe project-relative file containment, decoded geometry dimensions, and deterministic ToolingEntry construction. It never invokes Tooling/Bootstrap or changes selected-execution state. The next boundary is the FROZEN Tooling -> Bootstrap -> truthful READY composition.
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
@ -900,9 +902,10 @@ PRODUCT_DEFINITION_V1 PASS/FROZEN
PROMPT_TREE CURRENT PROMPT_TREE CURRENT
CALIBRATION_EVIDENCE_SOLVER_V1 IMPLEMENTED/VALIDATED CALIBRATION_EVIDENCE_SOLVER_V1 IMPLEMENTED/VALIDATED
CALIBRATION_TOOLING_ALIGNMENT PASS/FROZEN CALIBRATION_TOOLING_ALIGNMENT PASS/FROZEN
CURRENT_NEXT CALIBRATION_WORKFLOW_COORDINATOR CURRENT_NEXT CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY
``` ```
Implementation proceeds only through explicitly human-authorized tranches under `prompt.md` and the Implementation proceeds only through explicitly human-authorized tranches under `prompt.md` and the
numbered `prompt/` execution contract. The current authorized dependency is the final usable numbered `prompt/` execution contract. The current authorized dependency is the final usable
calibration workflow; its next missing sub-boundary is the workflow coordinator. calibration workflow; its next missing sub-boundary composes the FROZEN Tooling
and Bootstrap importer to reach truthful READY from the validated binding.

View file

@ -25,6 +25,7 @@ typedef enum {
LARDON3D_CALIBRATION_WORKFLOW_CAPACITY, LARDON3D_CALIBRATION_WORKFLOW_CAPACITY,
LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE, LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE,
LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH, LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH,
LARDON3D_CALIBRATION_WORKFLOW_PROJECT_DB_ERROR,
} Lardon3DCalibrationWorkflowResult; } Lardon3DCalibrationWorkflowResult;
/* `campaign_state_path` is a canonical external acquisition manifest: /* `campaign_state_path` is a canonical external acquisition manifest:
@ -110,6 +111,11 @@ typedef struct {
double holdout_rmse_px; double holdout_rmse_px;
double holdout_maximum_residual_px; double holdout_maximum_residual_px;
uint32_t extra_distortion_coefficient_count; uint32_t extra_distortion_coefficient_count;
/* Exact decoded/oriented geometry shared by every accepted calibration view.
* It is retained from detection.json + coordinate evidence and is never
* inferred from camera parameters or campaign images. */
uint32_t oriented_width;
uint32_t oriented_height;
const Lardon3DCalibrationToolingView *views; const Lardon3DCalibrationToolingView *views;
size_t view_count; size_t view_count;
const Lardon3DCalibrationToolingCoordinateCheck *coordinate_checks; const Lardon3DCalibrationToolingCoordinateCheck *coordinate_checks;
@ -137,6 +143,19 @@ lardon3d_calibration_workflow_materialize_external_evidence(
size_t coordinate_check_capacity, size_t coordinate_check_capacity,
Lardon3DCalibrationWorkflowExternalEvidence *output); Lardon3DCalibrationWorkflowExternalEvidence *output);
/* Bind already materialized external evidence to the exact durable selected
* execution. This stage is read-only: it verifies selected item order/Capture
* identity, explicit v23 optical assignment, managed representation
* size/SHA-256, safe project-relative regular-file access and the exact
* OpenCV-decoded oriented dimensions. Caller owns `entries`; `output` borrows
* them on success. No Tooling import or selected-execution mutation occurs. */
Lardon3DCalibrationWorkflowResult
lardon3d_calibration_workflow_bind_selected_execution(
Lardon3DProjectDb *database, const char *project_path,
const Lardon3DCalibrationWorkflowExternalEvidence *external,
Lardon3DCalibrationToolingEntry *entries, size_t entry_capacity,
Lardon3DCalibrationToolingEvidence *output);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View file

@ -182,6 +182,7 @@ lardon3d_app = executable(
'src/calibration_tooling.c', 'src/calibration_tooling.c',
'src/calibration_workflow.cpp', 'src/calibration_workflow.cpp',
'src/calibration_workflow_materialize.cpp', 'src/calibration_workflow_materialize.cpp',
'src/calibration_workflow_bind.cpp',
'src/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',
@ -927,6 +928,27 @@ test(
timeout: 30, timeout: 30,
) )
calibration_workflow_binding_test = executable(
'test-calibration-workflow-binding',
sources: [
'tests/test_calibration_workflow_bind.cpp',
'src/calibration_workflow_bind.cpp',
'src/optical_profiles.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, opencv],
)
test(
'calibration-workflow-binding',
calibration_workflow_binding_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_SELECTED_EXECUTION_BINDING CURRENT_IMPLEMENTATION_CURSOR=1_CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY
``` ```
## Authority ## Authority
@ -33,6 +33,7 @@ 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 workflow evidence materialization PASS/FROZEN
Calibration workflow selected-execution binding PASS/FROZEN
Calibration Tooling planarity alignment PASS/FROZEN Calibration Tooling planarity alignment PASS/FROZEN
``` ```

View file

@ -15,7 +15,8 @@ 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
CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN
CURRENT_CALIBRATION_NEXT=WORKFLOW_SELECTED_EXECUTION_BINDING_V1 CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS/FROZEN
CURRENT_CALIBRATION_NEXT=WORKFLOW_TOOLING_BOOTSTRAP_READY
``` ```
## Authority ## Authority
@ -58,7 +59,7 @@ dedicated physical calibration acquisition
-> real Sparse SfM -> real Sparse SfM
``` ```
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. The workflow coordinator now has three 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. Selected Execution Binding v1 is read-only: it proves the exact selected execution and Capture mapping, explicit optical configuration, managed representation size/SHA-256, safe project-relative file containment, decoded geometry dimensions, and deterministic `Lardon3DCalibrationToolingEntry` construction. Missing or mismatched evidence is rejected; nothing is inferred. The final remaining boundary composes the validated binding with the FROZEN Tooling and Bootstrap import path to reach truthful `READY`.
## 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_SELECTED_EXECUTION_BINDING CURRENT_NEXT=1_CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY
``` ```
## 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 and Evidence Materialization v1 PASS/FROZEN; current next sub-boundary: Selected Execution Binding v1; 1. final usable calibration workflow — IN PROGRESS; Input Boundary v1, Evidence Materialization v1 and Selected Execution Binding v1 PASS/FROZEN; current next sub-boundary: FROZEN Tooling -> Bootstrap -> truthful READY;
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,365 @@
// Calibration Workflow reaches Project DB through frozen Tooling/Bootstrap
// headers. Establish the C ABI before that transitive include path is guarded;
// project_db.c provides these symbols with C linkage.
extern "C" {
#include <lardon3d/project_db.h>
}
#include <lardon3d/calibration_workflow.h>
#include <lardon3d/optical_profiles.h>
#include <opencv2/imgcodecs.hpp>
#include <openssl/evp.h>
#include <cerrno>
#include <cstdint>
#include <cstring>
#include <fcntl.h>
#include <new>
#include <string>
#include <string_view>
#include <sys/stat.h>
#include <unistd.h>
#include <utility>
#include <vector>
namespace {
class OwnedFd {
public:
OwnedFd() = default;
explicit OwnedFd(int fd) : fd_(fd) {}
~OwnedFd() {
if (fd_ >= 0) (void)close(fd_);
}
OwnedFd(const OwnedFd&) = delete;
OwnedFd& operator=(const OwnedFd&) = delete;
OwnedFd(OwnedFd&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
OwnedFd& operator=(OwnedFd&& other) noexcept {
if (this != &other) {
if (fd_ >= 0) (void)close(fd_);
fd_ = other.fd_;
other.fd_ = -1;
}
return *this;
}
int get() const { return fd_; }
bool valid() const { return fd_ >= 0; }
private:
int fd_ = -1;
};
Lardon3DCalibrationWorkflowResult db_result(Lardon3DProjectDbResult result) {
if (result == LARDON3D_PROJECT_DB_OK)
return LARDON3D_CALIBRATION_WORKFLOW_OK;
if (result == LARDON3D_PROJECT_DB_NOT_FOUND ||
result == LARDON3D_PROJECT_DB_CONSTRAINT)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
return LARDON3D_CALIBRATION_WORKFLOW_PROJECT_DB_ERROR;
}
bool path_component_valid(std::string_view component) {
return !component.empty() && component != "." && component != "..";
}
Lardon3DCalibrationWorkflowResult open_project_asset(
const char *project_path, const char *relative_path, uint64_t expected_size,
const unsigned char expected_sha256[32], std::vector<unsigned char> *bytes) {
if (!project_path || !*project_path || !relative_path || !*relative_path ||
!expected_sha256 || !bytes)
return LARDON3D_CALIBRATION_WORKFLOW_INVALID_ARGUMENT;
std::string_view path(relative_path);
if (path.front() == '/' || path.back() == '/')
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
int root = open(project_path,
O_RDONLY | O_DIRECTORY | O_NONBLOCK | O_CLOEXEC | O_NOFOLLOW);
if (root < 0) {
if (errno == ELOOP || errno == ENOTDIR)
return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
OwnedFd current(root);
size_t at = 0;
while (at < path.size()) {
const size_t slash = path.find('/', at);
const bool last = slash == std::string_view::npos;
const size_t end = last ? path.size() : slash;
const std::string_view component = path.substr(at, end - at);
if (!path_component_valid(component))
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
const std::string name(component);
int flags = O_RDONLY | O_NONBLOCK | O_CLOEXEC | O_NOFOLLOW;
if (!last) flags |= O_DIRECTORY;
int next = openat(current.get(), name.c_str(), flags);
if (next < 0) {
if (errno == ELOOP || errno == ENOTDIR)
return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
OwnedFd opened(next);
struct stat st{};
if (fstat(opened.get(), &st) != 0)
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
if (!last) {
if (!S_ISDIR(st.st_mode))
return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
current = std::move(opened);
at = slash + 1;
continue;
}
if (!S_ISREG(st.st_mode))
return LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE;
if (st.st_size <= 0 || expected_size == 0 ||
static_cast<uint64_t>(st.st_size) != expected_size)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
if (expected_size > LARDON3D_CALIBRATION_WORKFLOW_MAX_FILE_BYTES)
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
bytes->assign(static_cast<size_t>(expected_size), 0);
size_t used = 0;
while (used < bytes->size()) {
ssize_t count = pread(opened.get(), bytes->data() + used,
bytes->size() - used, static_cast<off_t>(used));
if (count < 0 && errno == EINTR) continue;
if (count <= 0) return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
used += static_cast<size_t>(count);
}
unsigned char actual[32]{};
unsigned int digest_size = 0;
if (EVP_Digest(bytes->data(), bytes->size(), actual, &digest_size,
EVP_sha256(), nullptr) != 1 ||
digest_size != 32)
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
if (std::memcmp(actual, expected_sha256, 32) != 0)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
return LARDON3D_CALIBRATION_WORKFLOW_OK;
}
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
}
bool execution_stage_valid(const Lardon3DProjectDbSelectedExecution& execution) {
if (execution.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION)
return !execution.has_calibration_scope;
if (execution.stage == LARDON3D_SELECTED_EXECUTION_READY)
return execution.has_calibration_scope && execution.calibration_scope_id != 0;
return false;
}
void fill_entry(const Lardon3DCalibrationWorkflowExternalEvidence& external,
const Lardon3DProjectDbSelectedExecutionItem& item,
const Lardon3DProjectDbImageAsset& asset,
Lardon3DCalibrationToolingEntry *entry) {
std::memset(entry, 0, sizeof(*entry));
entry->image_id = item.image_id;
std::memcpy(entry->representation_sha256, asset.sha256, 32);
std::memcpy(entry->optical_state_sha256, external.optical_state_sha256, 32);
entry->width = external.oriented_width;
entry->height = external.oriented_height;
const double *p = external.repeated_parameters[0];
entry->fx = p[0];
entry->fy = p[1];
entry->cx = p[2];
entry->cy = p[3];
entry->k1 = p[4];
entry->k2 = p[5];
entry->p1 = p[6];
entry->p2 = p[7];
const double *fit = external.fit_parameters;
entry->fit_fx = fit[0];
entry->fit_fy = fit[1];
entry->fit_cx = fit[2];
entry->fit_cy = fit[3];
entry->fit_k1 = fit[4];
entry->fit_k2 = fit[5];
entry->fit_p1 = fit[6];
entry->fit_p2 = fit[7];
std::memcpy(entry->repeated_parameters, external.repeated_parameters,
sizeof(entry->repeated_parameters));
entry->support_images = external.support_images;
entry->support_observations = external.support_observations;
entry->reprojection_rmse_px = external.reprojection_rmse_px;
entry->maximum_parameter_delta = external.maximum_parameter_delta;
entry->validation_flags = external.validation_flags;
}
void fill_tooling_evidence(
const Lardon3DCalibrationWorkflowExternalEvidence& external,
const Lardon3DCalibrationToolingEntry *entries, size_t entry_count,
Lardon3DCalibrationToolingEvidence *output) {
std::memset(output, 0, sizeof(*output));
std::memcpy(output->target_sha256, external.target_sha256, 32);
std::memcpy(output->optical_state_sha256, external.optical_state_sha256, 32);
std::memcpy(output->solver_executable_sha256,
external.solver_executable_sha256, 32);
std::memcpy(output->solver_configuration_sha256,
external.solver_configuration_sha256, 32);
std::memcpy(output->initialization_evidence_sha256,
external.initialization_evidence_sha256, 32);
std::memcpy(output->validation_evidence_sha256,
external.validation_evidence_sha256, 32);
output->target_family = external.target_family;
output->target_squares_x = external.target_squares_x;
output->target_squares_y = external.target_squares_y;
output->target_square_length_mm = external.target_square_length_mm;
output->target_marker_length_mm = external.target_marker_length_mm;
output->target_active_width_mm = external.target_active_width_mm;
output->target_active_height_mm = external.target_active_height_mm;
output->target_white_border_mm = external.target_white_border_mm;
std::memcpy(output->target_measurements_mm, external.target_measurements_mm,
sizeof(output->target_measurements_mm));
output->measurement_resolution_mm = external.measurement_resolution_mm;
output->target_flatness_mm = external.target_flatness_mm;
output->holdout_rmse_px = external.holdout_rmse_px;
output->holdout_maximum_residual_px =
external.holdout_maximum_residual_px;
output->extra_distortion_coefficient_count =
external.extra_distortion_coefficient_count;
output->views = external.views;
output->view_count = external.view_count;
output->entries = entries;
output->entry_count = entry_count;
output->coordinate_checks = external.coordinate_checks;
output->coordinate_check_count = external.coordinate_check_count;
}
Lardon3DCalibrationWorkflowResult bind_selected_execution_impl(
Lardon3DProjectDb *database, const char *project_path,
const Lardon3DCalibrationWorkflowExternalEvidence *external,
Lardon3DCalibrationToolingEntry *entries, size_t entry_capacity,
Lardon3DCalibrationToolingEvidence *output) {
if (output) std::memset(output, 0, sizeof(*output));
if (!database || !project_path || !*project_path || !external || !entries ||
!output)
return LARDON3D_CALIBRATION_WORKFLOW_INVALID_ARGUMENT;
const uint32_t item_count = external->boundary.capture_count;
if (item_count == 0 ||
item_count > LARDON3D_CALIBRATION_WORKFLOW_MAX_SELECTED_ITEMS ||
item_count > entry_capacity)
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
if (!external->views || external->view_count == 0 ||
external->view_count > LARDON3D_CALIBRATION_TOOLING_MAX_VIEWS ||
!external->coordinate_checks || external->coordinate_check_count == 0 ||
external->coordinate_check_count >
LARDON3D_CALIBRATION_TOOLING_MAX_COORDINATE_CHECKS ||
external->oriented_width == 0 || external->oriented_height == 0)
return LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE;
if (std::memcmp(external->optical_state_sha256,
external->boundary.optical_state_sha256, 32) != 0)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
std::vector<Lardon3DCalibrationToolingEntry> staged(item_count);
Lardon3DProjectDbSelectedExecution execution{};
Lardon3DProjectDbResult dbr = lardon3d_project_db_load_selected_execution(
database, external->boundary.selected_execution_id, &execution);
if (dbr != LARDON3D_PROJECT_DB_OK) return db_result(dbr);
if (!execution_stage_valid(execution) ||
execution.next_item_index != execution.item_count ||
execution.item_count != item_count)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
Lardon3DOpticalConfiguration configuration{};
dbr = lardon3d_optical_configuration_load(
database, external->boundary.optical_configuration_id, &configuration);
if (dbr != LARDON3D_PROJECT_DB_OK) return db_result(dbr);
if (configuration.optical_configuration_id !=
external->boundary.optical_configuration_id)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
for (uint32_t index = 0; index < item_count; ++index) {
Lardon3DProjectDbSelectedExecutionItem item{};
dbr = lardon3d_project_db_load_selected_execution_item(
database, execution.execution_id, index, &item);
if (dbr != LARDON3D_PROJECT_DB_OK) return db_result(dbr);
if (item.item_index != index || !item.has_image || item.image_id == 0 ||
item.capture_id != external->boundary.capture_ids[index])
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
Lardon3DOpticalCaptureAssignment assignment{};
dbr = lardon3d_optical_capture_assignment_load(
database, item.capture_id, &assignment);
if (dbr != LARDON3D_PROJECT_DB_OK) return db_result(dbr);
if (assignment.capture_id != item.capture_id ||
assignment.optical_configuration_id !=
external->boundary.optical_configuration_id)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
if (assignment.has_campaign_origin &&
(assignment.campaign_task_id != execution.campaign_task_id ||
assignment.campaign_group_id != item.campaign_group_id))
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
Lardon3DProjectDbCapture image_capture{};
dbr = lardon3d_project_db_find_capture_for_image(
database, item.image_id, &image_capture);
if (dbr != LARDON3D_PROJECT_DB_OK) return db_result(dbr);
if (image_capture.capture_id != item.capture_id)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
Lardon3DProjectDbImage image{};
Lardon3DProjectDbImageAsset asset{};
dbr = lardon3d_project_db_load_image(database, item.image_id, &image, &asset);
if (dbr != LARDON3D_PROJECT_DB_OK) return db_result(dbr);
if (image.image_id != item.image_id || image.asset_id != asset.asset_id ||
asset.state != LARDON3D_DB_IMAGE_ASSET_READY)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
std::vector<unsigned char> bytes;
Lardon3DCalibrationWorkflowResult result =
open_project_asset(project_path, asset.path, asset.size_bytes,
asset.sha256, &bytes);
if (result != LARDON3D_CALIBRATION_WORKFLOW_OK) return result;
try {
cv::Mat decoded = cv::imdecode(bytes, cv::IMREAD_GRAYSCALE);
if (decoded.empty() || decoded.cols <= 0 || decoded.rows <= 0 ||
static_cast<uint32_t>(decoded.cols) != external->oriented_width ||
static_cast<uint32_t>(decoded.rows) != external->oriented_height)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
} catch (const cv::Exception&) {
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
} catch (...) {
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
fill_entry(*external, item, asset, &staged[index]);
}
std::memcpy(entries, staged.data(),
static_cast<size_t>(item_count) * sizeof(*entries));
fill_tooling_evidence(*external, entries, item_count, output);
return LARDON3D_CALIBRATION_WORKFLOW_OK;
}
} // namespace
extern "C" Lardon3DCalibrationWorkflowResult
lardon3d_calibration_workflow_bind_selected_execution(
Lardon3DProjectDb *database, const char *project_path,
const Lardon3DCalibrationWorkflowExternalEvidence *external,
Lardon3DCalibrationToolingEntry *entries, size_t entry_capacity,
Lardon3DCalibrationToolingEvidence *output) {
try {
return bind_selected_execution_impl(database, project_path, external,
entries, entry_capacity, output);
} catch (const std::bad_alloc&) {
if (output) std::memset(output, 0, sizeof(*output));
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
} catch (...) {
if (output) std::memset(output, 0, sizeof(*output));
return LARDON3D_CALIBRATION_WORKFLOW_IO_ERROR;
}
}

View file

@ -554,6 +554,8 @@ bool parse_detection(std::string_view text, const SessionData& session,
if (decision != "accepted" && decision != "rejected") return false; if (decision != "accepted" && decision != "rejected") return false;
if (dv.tooling.accepted) { if (dv.tooling.accepted) {
if (reason != "-" || !coordinate_pass || dv.width == 0 || dv.height == 0 || if (reason != "-" || !coordinate_pass || dv.width == 0 || dv.height == 0 ||
dv.width != sv.coordinate_width ||
dv.height != sv.coordinate_height ||
dv.tooling.orientation_degrees != sv.orientation || dv.tooling.orientation_degrees != sv.orientation ||
dv.tooling.corner_rms_px != sv.pre_solve || dv.tooling.corner_rms_px != sv.pre_solve ||
dv.tooling.distance_metres != sv.distance || dv.tooling.distance_metres != sv.distance ||
@ -846,14 +848,25 @@ lardon3d_calibration_workflow_materialize_external_evidence(
size_t accepted = 0; size_t accepted = 0;
uint64_t support_observations = 0; uint64_t support_observations = 0;
uint32_t oriented_width = 0;
uint32_t oriented_height = 0;
for (const DetectionView& view : parsed_views) { for (const DetectionView& view : parsed_views) {
if (view.tooling.accepted) { if (view.tooling.accepted) {
if (accepted == 0) {
oriented_width = view.width;
oriented_height = view.height;
} else if (view.width != oriented_width ||
view.height != oriented_height) {
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
}
++accepted; ++accepted;
support_observations += view.tooling.residual_count; support_observations += view.tooling.residual_count;
if (support_observations > UINT32_MAX) if (support_observations > UINT32_MAX)
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY; return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
} }
} }
if (accepted == 0 || oriented_width == 0 || oriented_height == 0)
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
SolveData solve; SolveData solve;
if (!parse_solve(solve_text, accepted, &solve)) if (!parse_solve(solve_text, accepted, &solve))
@ -906,6 +919,8 @@ lardon3d_calibration_workflow_materialize_external_evidence(
output->holdout_rmse_px = evidence.holdout_rmse; output->holdout_rmse_px = evidence.holdout_rmse;
output->holdout_maximum_residual_px = evidence.holdout_max; output->holdout_maximum_residual_px = evidence.holdout_max;
output->extra_distortion_coefficient_count = 0; output->extra_distortion_coefficient_count = 0;
output->oriented_width = oriented_width;
output->oriented_height = oriented_height;
output->views = views; output->views = views;
output->view_count = parsed_views.size(); output->view_count = parsed_views.size();
output->coordinate_checks = coordinate_checks; output->coordinate_checks = coordinate_checks;

View file

@ -0,0 +1,393 @@
// The fixture calls the C Project DB API directly. Include it with C linkage
// before Calibration Workflow's frozen Tooling/Bootstrap dependency chain.
extern "C" {
#include <lardon3d/project_db.h>
}
#include <lardon3d/calibration_workflow.h>
#include <lardon3d/optical_profiles.h>
#include <lardon3d/project_db.h>
#include <opencv2/imgcodecs.hpp>
#include <openssl/evp.h>
#include <sqlite3.h>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <unistd.h>
#define CHECK(x) do { if (!(x)) { \
std::fprintf(stderr, "workflow binding failure %d: %s\n", __LINE__, #x); \
return false; \
} } while (0)
namespace {
bool raw_sql(const std::filesystem::path& path, const std::string& sql) {
sqlite3 *database = nullptr;
if (sqlite3_open(path.c_str(), &database) != SQLITE_OK) return false;
const int code = sqlite3_exec(database, sql.c_str(), nullptr, nullptr, nullptr);
return sqlite3_close(database) == SQLITE_OK && code == SQLITE_OK;
}
bool digest(const std::vector<unsigned char>& bytes, unsigned char output[32]) {
unsigned int size = 0;
return EVP_Digest(bytes.data(), bytes.size(), output, &size,
EVP_sha256(), nullptr) == 1 && size == 32;
}
std::string hex(const unsigned char value[32]) {
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] & 15u];
}
return out;
}
bool write_bytes(const std::filesystem::path& path,
const std::vector<unsigned char>& bytes) {
std::ofstream out(path, std::ios::binary | std::ios::trunc);
out.write(reinterpret_cast<const char *>(bytes.data()),
static_cast<std::streamsize>(bytes.size()));
return static_cast<bool>(out);
}
struct Fixture {
std::filesystem::path root;
std::filesystem::path db_path;
std::filesystem::path image_path;
std::string image_asset_path;
Lardon3DProjectDb *database = nullptr;
uint64_t execution_id = 0;
uint64_t configuration_id = 0;
std::vector<unsigned char> image_bytes;
Lardon3DCalibrationToolingView view{};
Lardon3DCalibrationToolingCoordinateCheck check{};
Lardon3DCalibrationWorkflowExternalEvidence external{};
};
void close_fixture(Fixture *fixture) {
if (fixture->database) {
lardon3d_project_db_close(fixture->database);
fixture->database = nullptr;
}
if (!fixture->root.empty()) {
std::error_code ec;
std::filesystem::remove_all(fixture->root, ec);
}
}
bool seed_database_rows(const Fixture& fixture,
const unsigned char image_sha[32]) {
std::ostringstream sql;
sql << "INSERT INTO scansets(scanset_id,name,created_at,updated_at) "
"VALUES(1,'scope',1,1);"
<< "INSERT INTO tasks VALUES(1,'quality','photo_quality.triage',1,"
"5,5,100,1,0,0,0,0,1);"
<< "INSERT INTO tasks VALUES(2,'campaign','acquisition_campaign.run',1,"
"5,5,100,1,0,0,0,0,1);"
<< "INSERT INTO photo_quality_triage_tasks VALUES(1,1,2,1,X'01');"
<< "INSERT INTO photo_quality_triage_results VALUES("
"1,1,0,1,0,1,0,100,100,100,100,1.0,1.0,0.0,0.0,1.0,1.0,0.0,'GOOD');"
<< "INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02');"
<< "INSERT INTO captures(capture_id,scanset_id,created_at) VALUES(1,1,1);"
<< "INSERT INTO acquisition_campaign_captures VALUES(2,2,1);"
<< "INSERT INTO image_assets(asset_id,sha256,path,size_bytes,state,created_at) "
"VALUES(1,X'" << hex(image_sha)
<< "','" << fixture.image_asset_path << "'," << fixture.image_bytes.size()
<< ",1,1);"
<< "INSERT INTO images(image_id,scanset_id,asset_id,original_name,"
"source_path,imported_at) VALUES("
"1,1,1,'test.png','/source/test.png',1);"
<< "INSERT INTO capture_images VALUES(1,1);";
return raw_sql(fixture.db_path, sql.str());
}
bool create_optical_configuration(Fixture *fixture) {
Lardon3DOpticalCameraBodyProfile body_input{};
std::memcpy(body_input.manufacturer, "Generic", sizeof("Generic"));
std::memcpy(body_input.model, "Binding body", sizeof("Binding body"));
std::memcpy(body_input.name, "Binding body", sizeof("Binding body"));
Lardon3DOpticalCameraBodyProfile body{};
if (lardon3d_optical_camera_body_create(
fixture->database, &body_input, &body) != LARDON3D_PROJECT_DB_OK)
return false;
Lardon3DOpticalLensProfile lens_input{};
lens_input.interface_kind = LARDON3D_OPTICAL_LENS_MANUAL;
lens_input.focal_range_kind = LARDON3D_OPTICAL_FOCAL_RANGE_PRIME;
lens_input.minimum_focal_um = 35000;
lens_input.maximum_focal_um = 35000;
std::memcpy(lens_input.manufacturer, "Generic", sizeof("Generic"));
std::memcpy(lens_input.model, "Binding 35", sizeof("Binding 35"));
std::memcpy(lens_input.name, "Binding 35 mm", sizeof("Binding 35 mm"));
Lardon3DOpticalLensProfile lens{};
if (lardon3d_optical_lens_create(
fixture->database, &lens_input, &lens) != LARDON3D_PROJECT_DB_OK)
return false;
Lardon3DOpticalConfiguration config_input{};
config_input.camera_body_profile_id = body.camera_body_profile_id;
config_input.lens_profile_id = lens.lens_profile_id;
config_input.has_focal_length = true;
config_input.focal_length_um = 35000;
Lardon3DOpticalConfiguration configuration{};
if (lardon3d_optical_configuration_create(
fixture->database, &config_input, &configuration) !=
LARDON3D_PROJECT_DB_OK)
return false;
if (lardon3d_optical_capture_assign_explicit(
fixture->database, 1, configuration.optical_configuration_id) !=
LARDON3D_PROJECT_DB_OK)
return false;
fixture->configuration_id = configuration.optical_configuration_id;
return true;
}
void populate_external(Fixture *fixture, uint32_t width, uint32_t height) {
auto& e = fixture->external;
std::memset(&e, 0, sizeof(e));
e.boundary.selected_execution_id = fixture->execution_id;
e.boundary.optical_configuration_id = fixture->configuration_id;
e.boundary.capture_count = 1;
e.boundary.capture_ids[0] = 1;
std::memset(e.boundary.optical_state_sha256, 0x11, 32);
std::memset(e.optical_state_sha256, 0x11, 32);
std::memset(e.target_sha256, 0x21, 32);
std::memset(e.solver_executable_sha256, 0x31, 32);
std::memset(e.solver_configuration_sha256, 0x41, 32);
std::memset(e.initialization_evidence_sha256, 0x51, 32);
std::memset(e.validation_evidence_sha256, 0x61, 32);
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.0;
e.target_marker_length_mm = 21.0;
e.target_active_width_mm = 270.0;
e.target_active_height_mm = 210.0;
e.target_white_border_mm = 30.0;
for (double& measurement : e.target_measurements_mm) measurement = 30.0;
e.measurement_resolution_mm = 0.1;
e.target_flatness_mm = NAN;
e.holdout_rmse_px = 0.1;
e.holdout_maximum_residual_px = 0.2;
e.oriented_width = width;
e.oriented_height = height;
fixture->view.accepted = 1;
fixture->view.corner_count = 40;
fixture->view.residual_count = 40;
fixture->view.target_corner_quadrant_mask = 15;
fixture->view.target_occupancy = 0.3;
fixture->view.normal_angle_degrees = 25.0;
fixture->view.distance_metres = 0.4;
fixture->view.reprojection_rmse_px = 0.1;
fixture->view.maximum_residual_px = 0.2;
fixture->view.quadrant = 0;
fixture->view.distance_band = 1;
e.views = &fixture->view;
e.view_count = 1;
e.coordinate_checks = &fixture->check;
e.coordinate_check_count = 1;
const double parameters[8] = {
80.0, 81.0, 50.0, 40.0, 0.01, -0.01, 0.001, -0.001};
for (size_t run = 0; run < 3; ++run)
std::memcpy(e.repeated_parameters[run], parameters, sizeof(parameters));
std::memcpy(e.fit_parameters, parameters, sizeof(parameters));
e.support_images = 40;
e.support_observations = 1600;
e.reprojection_rmse_px = 0.1;
e.maximum_residual_px = 0.2;
e.maximum_parameter_delta = 0.0;
e.validation_flags = LARDON3D_CALIBRATION_TOOLING_VALIDATION_FLAGS;
}
bool make_fixture(Fixture *fixture, uint32_t width = 100,
uint32_t height = 80, bool complete = true) {
char temp[] = "/tmp/lardon3d-workflow-bind-XXXXXX";
char *root = mkdtemp(temp);
if (!root) return false;
fixture->root = root;
fixture->db_path = fixture->root / "project.db";
cv::Mat image(static_cast<int>(height), static_cast<int>(width),
CV_8UC1, cv::Scalar(127));
if (!cv::imencode(".png", image, fixture->image_bytes))
return false;
unsigned char image_sha[32]{};
if (!digest(fixture->image_bytes, image_sha)) return false;
const std::string image_hex = hex(image_sha);
fixture->image_asset_path =
"assets/images/" + image_hex.substr(0, 2) + "/" + image_hex;
fixture->image_path = fixture->root / fixture->image_asset_path;
if (!std::filesystem::create_directories(fixture->image_path.parent_path()) ||
!write_bytes(fixture->image_path, fixture->image_bytes))
return false;
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
if (lardon3d_project_db_open(
fixture->db_path.c_str(), &fixture->database, error) !=
LARDON3D_PROJECT_DB_OK)
return false;
lardon3d_project_db_close(fixture->database);
fixture->database = nullptr;
if (!seed_database_rows(*fixture, image_sha)) return false;
if (lardon3d_project_db_open(
fixture->db_path.c_str(), &fixture->database, error) !=
LARDON3D_PROJECT_DB_OK)
return false;
if (!create_optical_configuration(fixture)) return false;
if (lardon3d_project_db_set_selected_capture_image(
fixture->database, 1, 1) != LARDON3D_PROJECT_DB_OK)
return false;
Lardon3DProjectDbSelectedExecutionItem item{};
item.item_index = 0;
item.quality_group_id = 1;
item.campaign_group_id = 2;
item.capture_id = 1;
item.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE;
item.source_asset_id = 0;
Lardon3DProjectDbSelectedExecution execution{};
if (lardon3d_project_db_create_selected_execution(
fixture->database, 1, 2, &item, 1, 10, &execution) !=
LARDON3D_PROJECT_DB_OK)
return false;
fixture->execution_id = execution.execution_id;
if (complete &&
lardon3d_project_db_record_selected_representation(
fixture->database, fixture->execution_id, 0, 1, 1) !=
LARDON3D_PROJECT_DB_OK)
return false;
populate_external(fixture, width, height);
return true;
}
bool valid_binding() {
Fixture fixture;
CHECK(make_fixture(&fixture));
Lardon3DProjectDbSelectedExecution before{};
CHECK(lardon3d_project_db_load_selected_execution(
fixture.database, fixture.execution_id, &before) ==
LARDON3D_PROJECT_DB_OK);
CHECK(before.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION);
CHECK(!before.has_calibration_scope);
Lardon3DCalibrationToolingEntry entries[1]{};
Lardon3DCalibrationToolingEvidence tooling{};
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &fixture.external,
entries, 1, &tooling) == LARDON3D_CALIBRATION_WORKFLOW_OK);
CHECK(tooling.entries == entries && tooling.entry_count == 1);
CHECK(tooling.views == fixture.external.views && tooling.view_count == 1);
CHECK(entries[0].image_id == 1);
CHECK(entries[0].width == 100 && entries[0].height == 80);
CHECK(entries[0].fx == 80.0 && entries[0].fy == 81.0);
CHECK(entries[0].fit_fx == 80.0);
CHECK(entries[0].support_images == 40 &&
entries[0].support_observations == 1600);
CHECK(entries[0].validation_flags ==
LARDON3D_CALIBRATION_TOOLING_VALIDATION_FLAGS);
Lardon3DCalibrationToolingEntry second[1]{};
Lardon3DCalibrationToolingEvidence second_tooling{};
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &fixture.external,
second, 1, &second_tooling) == LARDON3D_CALIBRATION_WORKFLOW_OK);
CHECK(std::memcmp(entries, second, sizeof(entries)) == 0);
Lardon3DProjectDbSelectedExecution after{};
CHECK(lardon3d_project_db_load_selected_execution(
fixture.database, fixture.execution_id, &after) ==
LARDON3D_PROJECT_DB_OK);
CHECK(after.stage == before.stage &&
after.next_item_index == before.next_item_index &&
after.has_calibration_scope == before.has_calibration_scope &&
after.calibration_scope_id == before.calibration_scope_id);
close_fixture(&fixture);
return true;
}
bool mismatch_rejections() {
Fixture fixture;
CHECK(make_fixture(&fixture));
Lardon3DCalibrationToolingEntry entries[1]{};
Lardon3DCalibrationToolingEvidence tooling{};
auto changed = fixture.external;
changed.boundary.capture_ids[0] = 999;
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &changed,
entries, 1, &tooling) ==
LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
changed = fixture.external;
++changed.boundary.optical_configuration_id;
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &changed,
entries, 1, &tooling) ==
LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
changed = fixture.external;
++changed.oriented_width;
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &changed,
entries, 1, &tooling) ==
LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &fixture.external,
entries, 0, &tooling) == LARDON3D_CALIBRATION_WORKFLOW_CAPACITY);
const auto backing = fixture.image_path.parent_path() / "backing.png";
std::error_code rename_error;
std::filesystem::rename(fixture.image_path, backing, rename_error);
CHECK(!rename_error);
CHECK(symlink("backing.png", fixture.image_path.c_str()) == 0);
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &fixture.external,
entries, 1, &tooling) ==
LARDON3D_CALIBRATION_WORKFLOW_NON_REGULAR_FILE);
CHECK(unlink(fixture.image_path.c_str()) == 0);
rename_error.clear();
std::filesystem::rename(backing, fixture.image_path, rename_error);
CHECK(!rename_error);
close_fixture(&fixture);
CHECK(make_fixture(&fixture, 100, 80, false));
CHECK(lardon3d_calibration_workflow_bind_selected_execution(
fixture.database, fixture.root.c_str(), &fixture.external,
entries, 1, &tooling) ==
LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH);
close_fixture(&fixture);
return true;
}
} // namespace
int main() {
if (!valid_binding() || !mismatch_rejections()) return 1;
std::puts("CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS");
return 0;
}

View file

@ -229,6 +229,7 @@ bool valid() {
CHECK(lardon3d_calibration_workflow_materialize_external_evidence( CHECK(lardon3d_calibration_workflow_materialize_external_evidence(
&f.files,views,2,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_OK); &f.files,views,2,checks,40,&out)==LARDON3D_CALIBRATION_WORKFLOW_OK);
CHECK(out.view_count==2 && out.coordinate_check_count==40); CHECK(out.view_count==2 && out.coordinate_check_count==40);
CHECK(out.oriented_width==1000 && out.oriented_height==800);
CHECK(out.support_images==2 && out.support_observations==40); CHECK(out.support_images==2 && out.support_observations==40);
CHECK(out.validation_flags==15 && out.target_white_border_mm==30.0); 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(out.repeated_parameters[0][0]==1000.0 && out.fit_parameters[0]==1000.0);