feat: bind calibration workflow selected execution
This commit is contained in:
parent
6a670fd9e9
commit
df7b589174
11 changed files with 868 additions and 28 deletions
|
|
@ -6,7 +6,8 @@
|
|||
CALIBRATION_WORKFLOW=IN_PROGRESS
|
||||
CALIBRATION_WORKFLOW_INPUT_BOUNDARY_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
|
||||
|
|
@ -154,28 +155,47 @@ explicit Project DB optical configuration.
|
|||
|
||||
Absence or disagreement remains `CALIBRATION_UNAVAILABLE`.
|
||||
|
||||
## Current next boundary
|
||||
## Selected Execution Binding v1
|
||||
|
||||
```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
|
||||
appropriate to the existing FROZEN contract;
|
||||
- require exact selected item count and order;
|
||||
- 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.
|
||||
```text
|
||||
src/calibration_workflow_bind.cpp
|
||||
tests/test_calibration_workflow_bind.cpp
|
||||
```
|
||||
|
||||
Only after that read-only binding passes may a final workflow boundary invoke
|
||||
the FROZEN Tooling/Bootstrap path and transition the selected execution to
|
||||
truthful `READY`.
|
||||
This boundary is read-only. It may read Project DB and managed representation
|
||||
bytes, but does not attach a calibration scope, invoke Calibration Tooling or
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ SOURCE_COMMENT_AUDIT PASS
|
|||
PRODUCT_DEFINITION PASS/FROZEN
|
||||
PROMPT_TREE CURRENT
|
||||
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:
|
||||
|
|
@ -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_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
|
||||
defines a categorical physical planarity attestation, not a numeric flatness threshold. Tooling
|
||||
therefore rejects invented finite `target_flatness_mm` values. `L3DCALB1` v1 and Calibration
|
||||
|
|
@ -900,9 +902,10 @@ PRODUCT_DEFINITION_V1 PASS/FROZEN
|
|||
PROMPT_TREE CURRENT
|
||||
CALIBRATION_EVIDENCE_SOLVER_V1 IMPLEMENTED/VALIDATED
|
||||
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
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ typedef enum {
|
|||
LARDON3D_CALIBRATION_WORKFLOW_CAPACITY,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_MALFORMED_EVIDENCE,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_PROJECT_DB_ERROR,
|
||||
} Lardon3DCalibrationWorkflowResult;
|
||||
|
||||
/* `campaign_state_path` is a canonical external acquisition manifest:
|
||||
|
|
@ -110,6 +111,11 @@ typedef struct {
|
|||
double holdout_rmse_px;
|
||||
double holdout_maximum_residual_px;
|
||||
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;
|
||||
size_t view_count;
|
||||
const Lardon3DCalibrationToolingCoordinateCheck *coordinate_checks;
|
||||
|
|
@ -137,6 +143,19 @@ lardon3d_calibration_workflow_materialize_external_evidence(
|
|||
size_t coordinate_check_capacity,
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
22
meson.build
22
meson.build
|
|
@ -182,6 +182,7 @@ lardon3d_app = executable(
|
|||
'src/calibration_tooling.c',
|
||||
'src/calibration_workflow.cpp',
|
||||
'src/calibration_workflow_materialize.cpp',
|
||||
'src/calibration_workflow_bind.cpp',
|
||||
'src/sparse_sfm_geometry.cpp',
|
||||
'src/sparse_sfm_incremental.cpp',
|
||||
'src/sparse_sfm_bundle_adjustment.cpp',
|
||||
|
|
@ -927,6 +928,27 @@ test(
|
|||
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(
|
||||
'test-optical-profiles',
|
||||
sources: [
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
CURRENT_PROJECT_DB_SCHEMA=v25
|
||||
PRODUCTION_TASK_KINDS=16
|
||||
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
|
||||
|
|
@ -33,6 +33,7 @@ Calibration solver per-view evidence PASS/FROZEN
|
|||
Calibration solver bundle repair PASS/FROZEN
|
||||
Calibration workflow input boundary PASS/FROZEN
|
||||
Calibration workflow evidence materialization PASS/FROZEN
|
||||
Calibration workflow selected-execution binding PASS/FROZEN
|
||||
Calibration Tooling planarity alignment PASS/FROZEN
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@ CALIBRATION_SOLVER_BUNDLE_REPAIR_V1=PASS/FROZEN
|
|||
CALIBRATION_WORKFLOW=IN_PROGRESS
|
||||
CALIBRATION_WORKFLOW_INPUT_BOUNDARY_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
|
||||
|
|
@ -58,7 +59,7 @@ dedicated physical calibration acquisition
|
|||
-> 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
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
IMPLEMENTATION_ORDER=DEPENDENCY_DRIVEN
|
||||
IMPLEMENTATION_AUTHORIZATION=NO
|
||||
STEP_0_USER_FACING_LANGUAGE_NORMALIZATION=PASS
|
||||
CURRENT_NEXT=1_CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING
|
||||
CURRENT_NEXT=1_CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY
|
||||
```
|
||||
|
||||
## Authority
|
||||
|
|
@ -20,7 +20,7 @@ Implementation remains unauthorized until the human explicitly authorizes a tran
|
|||
Default dependency order:
|
||||
|
||||
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;
|
||||
3. real Sparse SfM proof;
|
||||
4. durable Dense/OpenMVS orchestration;
|
||||
|
|
|
|||
365
src/calibration_workflow_bind.cpp
Normal file
365
src/calibration_workflow_bind.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -554,6 +554,8 @@ bool parse_detection(std::string_view text, const SessionData& session,
|
|||
if (decision != "accepted" && decision != "rejected") return false;
|
||||
if (dv.tooling.accepted) {
|
||||
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.corner_rms_px != sv.pre_solve ||
|
||||
dv.tooling.distance_metres != sv.distance ||
|
||||
|
|
@ -846,14 +848,25 @@ lardon3d_calibration_workflow_materialize_external_evidence(
|
|||
|
||||
size_t accepted = 0;
|
||||
uint64_t support_observations = 0;
|
||||
uint32_t oriented_width = 0;
|
||||
uint32_t oriented_height = 0;
|
||||
for (const DetectionView& view : parsed_views) {
|
||||
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;
|
||||
support_observations += view.tooling.residual_count;
|
||||
if (support_observations > UINT32_MAX)
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_CAPACITY;
|
||||
}
|
||||
}
|
||||
if (accepted == 0 || oriented_width == 0 || oriented_height == 0)
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_PROVENANCE_MISMATCH;
|
||||
|
||||
SolveData 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_maximum_residual_px = evidence.holdout_max;
|
||||
output->extra_distortion_coefficient_count = 0;
|
||||
output->oriented_width = oriented_width;
|
||||
output->oriented_height = oriented_height;
|
||||
output->views = views;
|
||||
output->view_count = parsed_views.size();
|
||||
output->coordinate_checks = coordinate_checks;
|
||||
|
|
|
|||
393
tests/test_calibration_workflow_bind.cpp
Normal file
393
tests/test_calibration_workflow_bind.cpp
Normal 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;
|
||||
}
|
||||
|
|
@ -229,6 +229,7 @@ bool valid() {
|
|||
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.oriented_width==1000 && out.oriented_height==800);
|
||||
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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue