feat: complete heterogeneous calibration workflow
This commit is contained in:
parent
a77d095a85
commit
038cc64032
13 changed files with 898 additions and 33 deletions
|
|
@ -11,7 +11,7 @@ REAL_S21_TRACKS=PASS/FROZEN
|
|||
REAL_A6000_PRE_SFM=PASS/FROZEN
|
||||
PRODUCT_DEFINITION_V1=PASS/FROZEN
|
||||
PROMPT_TREE=CURRENT
|
||||
CURRENT_NEXT=CALIBRATION_V2_WORKFLOW_READY
|
||||
CURRENT_NEXT=AUTOFOCUS_V2_FOUNDATION
|
||||
```
|
||||
|
||||
This index separates current authority, historical evidence and future product-definition work.
|
||||
|
|
|
|||
|
|
@ -82,8 +82,36 @@ Bootstrap validates the whole artifact and then verifies every entry against
|
|||
the immutable selected execution's exact item index, `image_id`, and current
|
||||
representation asset SHA-256 before its first write. It creates or reuses the
|
||||
existing content-addressed Sparse calibrations per entry, creates or reuses one
|
||||
scope over every selected image across all groups, and attaches only that
|
||||
complete scope. A pre-publication error attaches no scope. A later failure may
|
||||
retain immutable calibration evidence but cannot make the execution READY.
|
||||
Exact retries reuse calibrations and the scope and converge on the same
|
||||
attachment.
|
||||
scope over every selected image across all groups. The original Bootstrap v2
|
||||
import attaches that complete scope and retains its existing public behavior.
|
||||
The separately named `publish_unattached` primitive returns the exact
|
||||
selected-item/image/calibration mapping without attaching the scope; it exists
|
||||
only so Workflow v2 can establish v26 compatibility before READY. A
|
||||
pre-publication error attaches no scope. A later failure may retain immutable
|
||||
calibration evidence but cannot make the execution READY. Exact retries reuse
|
||||
calibrations and the scope and converge on the same attachment.
|
||||
|
||||
## Heterogeneous Workflow v2 composition
|
||||
|
||||
Workflow v2 consumes the L3DCALB2 artifact and a complete caller-owned binding
|
||||
for every selected item. Each binding repeats the selected execution's durable
|
||||
`item_index -> capture_id` relation. Capture identity is never recovered from
|
||||
an image ID, path, SHA-256, filename, artifact group identity, or numeric group
|
||||
ID.
|
||||
|
||||
Before unattached publication, Workflow v2 requires complete observed v26
|
||||
geometric state for every selected Capture. Automatic resolution reports zero
|
||||
exact candidates as `CALIBRATION_REQUIRED` and multiple exact candidates as
|
||||
`SELECTION_REQUIRED`. Explicit existing applicability must be exactly
|
||||
compatible. Explicit publication creates/reuses deterministic profile metadata
|
||||
from the immutable artifact calibration and its group-local provenance, then
|
||||
creates/reuses applicability from the declared exact-state exemplar. It does
|
||||
not create scientific Capture identity or authorize interpolation.
|
||||
|
||||
For every selected item, the final resolved v26 sparse `calibration_id` must
|
||||
equal that image's calibration member in the complete unattached scope. Only
|
||||
after all items pass that equality does Workflow v2 attach the scope and return
|
||||
`READY`. Invalid evidence or a wrong assignment is a distinct non-ready error.
|
||||
Any earlier failure leaves the selected execution unattached; exact retries are
|
||||
deterministic and converge through the immutable Bootstrap/profile/
|
||||
applicability/selection APIs. No Project DB schema change is required.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@ CALIBRATION_WORKFLOW_INPUT_BOUNDARY_V1=PASS/FROZEN
|
|||
CALIBRATION_WORKFLOW_EVIDENCE_MATERIALIZATION_V1=PASS/FROZEN
|
||||
CALIBRATION_WORKFLOW_SELECTED_EXECUTION_BINDING_V1=PASS/FROZEN
|
||||
CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY_V1=PASS/FROZEN
|
||||
CURRENT_WORKFLOW_NEXT=DEDICATED_PHYSICAL_CALIBRATED_REAL_CAMPAIGN
|
||||
CALIBRATION_WORKFLOW_V2=PASS/FROZEN
|
||||
CURRENT_WORKFLOW_NEXT=ADAPTIVE_CAPTURE_SETTINGS_AND_AUTOFOCUS_V2_FOUNDATION
|
||||
```
|
||||
|
||||
## Authority
|
||||
|
|
@ -201,6 +202,32 @@ The software workflow is PASS/FROZEN. It does not establish physical evidence:
|
|||
historical S21/A6000 campaigns remain CALIBRATION_UNAVAILABLE and
|
||||
BLOCKED_BY_KNOWN_CALIBRATION_DATA.
|
||||
|
||||
## Additive heterogeneous Workflow v2
|
||||
|
||||
`CALIBRATION_WORKFLOW_V2=PASS/FROZEN`. This additive composition leaves every
|
||||
v1 API, artifact and workflow meaning unchanged. It composes v26 Capture
|
||||
geometric state/applicability with the L3DCALB2 publication path for one
|
||||
heterogeneous selected execution.
|
||||
|
||||
The workflow first verifies the exact durable selected-item-to-Capture mapping
|
||||
for every caller binding. It never recovers Capture identity from an image ID,
|
||||
path, SHA-256, filename or operational group ID. Every Capture must have a
|
||||
complete observed geometric state. Missing or incomplete state reports
|
||||
`CALIBRATION_REQUIRED`; multiple exact compatible applicability candidates
|
||||
report `SELECTION_REQUIRED`.
|
||||
|
||||
L3DCALB2 is first published through its additive unattached primitive. This
|
||||
fully validates artifact bytes and selected image/representation bindings and
|
||||
may create reusable immutable calibrations and a complete scope, but cannot
|
||||
transition the execution to READY. Workflow v2 then binds each returned
|
||||
per-image calibration to an exact v26 applicability/selection and verifies the
|
||||
resolved `sparse_calibration_id` equals that scope member. Only after every
|
||||
selected item passes may the existing scope-attachment transaction set READY.
|
||||
|
||||
Invalid artifact evidence and wrong optical assignments are distinct non-ready
|
||||
errors. No pre-final failure attaches a scope. Exact retries reuse immutable
|
||||
calibrations and the same complete scope deterministically.
|
||||
|
||||
## Current next boundary
|
||||
|
||||
```text
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ SOURCE_COMMENT_AUDIT PASS
|
|||
PRODUCT_DEFINITION PASS/FROZEN
|
||||
PROMPT_TREE CURRENT
|
||||
USER_FACING_UI_LANGUAGE_NORMALIZATION PASS
|
||||
CURRENT_NEXT CALIBRATION_V2_WORKFLOW_READY
|
||||
CURRENT_NEXT AUTOFOCUS_V2_FOUNDATION
|
||||
```
|
||||
|
||||
The current Project DB head is additive:
|
||||
|
|
@ -537,8 +537,10 @@ applicability while retaining that per-image scope model. Unknown state remains
|
|||
calibration publication. `CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PASS/FROZEN`:
|
||||
additive L3DCALB2/Tooling/Bootstrap v2 now retains independent group-local provenance, validates
|
||||
complete exact selected-image coverage, and attaches one existing per-image scope only after complete
|
||||
publication. The next dependency is heterogeneous Workflow v2 truthful READY. Device-specific
|
||||
autofocus envelopes remain blocked until physical evidence validates them.
|
||||
publication. `CALIBRATION_WORKFLOW_V2=PASS/FROZEN`: the workflow now proves exact Capture/state
|
||||
applicability and matching per-image calibration IDs before its sole final scope attachment. The next
|
||||
dependency is the generic adaptive-settings/autofocus foundation. Device-specific autofocus envelopes
|
||||
remain blocked until physical evidence validates them.
|
||||
|
||||
Calibration Tooling v1 consumes an already acquired Science v1 evidence bundle, validates the bounded
|
||||
contract and produces deterministic `L3DCALB1` v1.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,33 @@ typedef struct {
|
|||
uint32_t group_count;
|
||||
} Lardon3DCalibrationBootstrapV2Output;
|
||||
|
||||
typedef struct {
|
||||
uint32_t selected_item_index;
|
||||
uint64_t image_id;
|
||||
uint64_t calibration_id;
|
||||
} Lardon3DCalibrationBootstrapV2Member;
|
||||
|
||||
/* Validate and publish one complete artifact without attaching its scope.
|
||||
* `members` is caller-owned storage of at least V2_MAX_ENTRIES only when that
|
||||
* many entries are accepted; `member_capacity` may be smaller and yields
|
||||
* INVALID_ARGUMENT before database mutation when the artifact will not fit.
|
||||
* Successful members are ordered by selected_item_index and expose the exact
|
||||
* immutable calibration created/reused for each selected image.
|
||||
*
|
||||
* This primitive exists so Workflow v2 can establish exact Capture-owned v26
|
||||
* applicability and selection before the single READY attachment. It never
|
||||
* infers Capture identity from image, path, digest, or group identity, and it
|
||||
* never attaches the returned complete scope. Exact retry is deterministic;
|
||||
* immutable calibrations and the unattached scope may survive later workflow
|
||||
* failure under the existing publication contract. */
|
||||
Lardon3DCalibrationBootstrapV2Result
|
||||
lardon3d_calibration_bootstrap_v2_publish_unattached(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char expected_artifact_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
||||
Lardon3DCalibrationBootstrapV2Member *members, size_t member_capacity,
|
||||
Lardon3DCalibrationBootstrapV2Output *output);
|
||||
|
||||
/* Import one complete L3DCALB2 artifact for an immutable selected execution.
|
||||
* All pointers are required and borrowed only for the call; artifact_size is
|
||||
* bounded by V2_MAX_BYTES and must match expected_artifact_sha256 before any
|
||||
|
|
|
|||
80
include/lardon3d/calibration_workflow_v2.h
Normal file
80
include/lardon3d/calibration_workflow_v2.h
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#ifndef LARDON3D_CALIBRATION_WORKFLOW_V2_H
|
||||
#define LARDON3D_CALIBRATION_WORKFLOW_V2_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <lardon3d/calibration_bootstrap_v2.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC = 1,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_EXISTING_EXPLICIT = 2,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT = 3,
|
||||
} Lardon3DCalibrationWorkflowV2BindingKind;
|
||||
|
||||
typedef struct {
|
||||
uint32_t selected_item_index;
|
||||
/* Capture identity must be copied from the selected execution's durable item
|
||||
* mapping. Workflow rejects any mismatch and never derives it from image_id,
|
||||
* artifact group number, path, filename, or SHA-256. */
|
||||
uint64_t capture_id;
|
||||
Lardon3DCalibrationWorkflowV2BindingKind kind;
|
||||
/* EXISTING_EXPLICIT requires an existing exact v26 applicability. */
|
||||
uint64_t applicability_id;
|
||||
/* PUBLISH_EXPLICIT uses this Capture's complete observed tuple as the exact
|
||||
* applicability exemplar. It may equal capture_id. Other modes require 0. */
|
||||
uint64_t exemplar_capture_id;
|
||||
} Lardon3DCalibrationWorkflowV2Binding;
|
||||
|
||||
typedef enum {
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_READY = 0,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_EVIDENCE,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR,
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_OUT_OF_MEMORY,
|
||||
} Lardon3DCalibrationWorkflowV2Result;
|
||||
|
||||
typedef struct {
|
||||
Lardon3DCalibrationBootstrapV2Output publication;
|
||||
uint32_t selected_item_count;
|
||||
} Lardon3DCalibrationWorkflowV2Output;
|
||||
|
||||
/* Compose heterogeneous L3DCALB2 publication with exact Project DB v26
|
||||
* applicability and the final selected-execution READY transition.
|
||||
*
|
||||
* All pointers are borrowed for the call. `bindings` must cover every selected
|
||||
* item exactly once and must repeat the durable selected item -> Capture
|
||||
* mapping. AUTOMATIC accepts exactly one compatible applicability and reports
|
||||
* CALIBRATION_REQUIRED/SELECTION_REQUIRED truthfully for zero/multiple choices.
|
||||
* EXISTING_EXPLICIT verifies and durably selects the requested exact
|
||||
* applicability. PUBLISH_EXPLICIT creates/reuses deterministic optical profile
|
||||
* metadata for the artifact calibration, creates/reuses exact exemplar
|
||||
* applicability, and selects it explicitly.
|
||||
*
|
||||
* READY is returned only when every durable Capture resolves to the same
|
||||
* calibration_id as its complete scope member and that exact scope is attached.
|
||||
* Invalid evidence or an assignment mismatch is a distinct non-ready error.
|
||||
* Pre-attachment failure never attaches a scope; immutable publication and
|
||||
* optical evidence created by an earlier phase may remain and exact retry
|
||||
* converges. No schema change, solver execution, or Capture inference occurs.
|
||||
*/
|
||||
Lardon3DCalibrationWorkflowV2Result lardon3d_calibration_workflow_v2_complete(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char
|
||||
expected_artifact_sha256[LARDON3D_PROJECT_DB_SHA256_SIZE],
|
||||
const Lardon3DCalibrationWorkflowV2Binding *bindings, size_t binding_count,
|
||||
Lardon3DCalibrationWorkflowV2Output *output);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
18
meson.build
18
meson.build
|
|
@ -180,6 +180,7 @@ lardon3d_app = executable(
|
|||
'src/optical_profiles.c',
|
||||
'src/calibration_bootstrap.c',
|
||||
'src/calibration_bootstrap_v2.c',
|
||||
'src/calibration_workflow_v2.c',
|
||||
'src/calibration_tooling.c',
|
||||
'src/calibration_tooling_v2.c',
|
||||
'src/calibration_workflow.cpp',
|
||||
|
|
@ -916,6 +917,23 @@ calibration_publication_v2_test = executable(
|
|||
|
||||
test('calibration-publication-v2', calibration_publication_v2_test, timeout: 30)
|
||||
|
||||
calibration_workflow_v2_test = executable(
|
||||
'test-calibration-workflow-v2',
|
||||
sources: [
|
||||
'tests/test_calibration_workflow_v2.c',
|
||||
'src/calibration_workflow_v2.c',
|
||||
'src/calibration_tooling_v2.c', 'src/calibration_bootstrap_v2.c',
|
||||
'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, cc.find_library('m')],
|
||||
)
|
||||
|
||||
test('calibration-workflow-v2', calibration_workflow_v2_test, timeout: 30)
|
||||
|
||||
calibration_workflow_test = executable(
|
||||
'test-calibration-workflow',
|
||||
sources: [
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
CURRENT_PROJECT_DB_SCHEMA=v26
|
||||
PRODUCTION_TASK_KINDS=16
|
||||
USER_FACING_UI_LANGUAGE_NORMALIZATION=PASS
|
||||
CURRENT_IMPLEMENTATION_CURSOR=CALIBRATION_V2_WORKFLOW_READY
|
||||
CURRENT_IMPLEMENTATION_CURSOR=ADAPTIVE_CAPTURE_SETTINGS_AND_AUTOFOCUS_V2_FOUNDATION
|
||||
```
|
||||
|
||||
## Authority
|
||||
|
|
@ -38,6 +38,7 @@ Calibration workflow Tooling/Bootstrap READY PASS/FROZEN
|
|||
Calibration Science v2 heterogeneous optics PASS/FROZEN
|
||||
Calibration v2 optical-state foundation PASS/FROZEN
|
||||
Calibration v2 heterogeneous publication PASS/FROZEN
|
||||
Calibration Workflow v2 PASS/FROZEN
|
||||
Adaptive capture settings semantics PLANNED
|
||||
Autofocus v2 foundation PLANNED
|
||||
Calibration Tooling planarity alignment PASS/FROZEN
|
||||
|
|
|
|||
|
|
@ -20,10 +20,10 @@ CALIBRATION_WORKFLOW_TOOLING_BOOTSTRAP_READY_V1=PASS/FROZEN
|
|||
CALIBRATION_SCIENCE_V2=PASS/FROZEN
|
||||
CALIBRATION_V2_HETEROGENEOUS_OPTICS=PASS/FROZEN
|
||||
CALIBRATION_V2_HETEROGENEOUS_CALIBRATION_PUBLICATION=PASS/FROZEN
|
||||
CALIBRATION_V2_WORKFLOW_READY=PLANNED
|
||||
CALIBRATION_V2_WORKFLOW_READY=PASS/FROZEN
|
||||
ADAPTIVE_CAPTURE_SETTINGS_CONTRACT=PLANNED
|
||||
AUTOFOCUS_V2_FOUNDATION=PLANNED
|
||||
CURRENT_CALIBRATION_NEXT=CALIBRATION_V2_WORKFLOW_READY
|
||||
CURRENT_CALIBRATION_NEXT=AUTOFOCUS_V2_FOUNDATION
|
||||
```
|
||||
|
||||
## Authority
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
IMPLEMENTATION_ORDER=DEPENDENCY_DRIVEN
|
||||
IMPLEMENTATION_AUTHORIZATION=NO
|
||||
STEP_0_USER_FACING_LANGUAGE_NORMALIZATION=PASS
|
||||
CURRENT_NEXT=CALIBRATION_V2_WORKFLOW_READY
|
||||
CURRENT_NEXT=AUTOFOCUS_V2_FOUNDATION
|
||||
```
|
||||
|
||||
## Authority
|
||||
|
|
@ -24,23 +24,24 @@ Default dependency order:
|
|||
2. Calibration Science v2 design for heterogeneous cameras/lenses/focals, adaptive capture settings and autofocus — PASS/FROZEN;
|
||||
3. Calibration v2 heterogeneous-optics persistence foundation — PASS/FROZEN;
|
||||
4. Heterogeneous calibration publication / Tooling / Bootstrap evolution — PASS/FROZEN;
|
||||
5. Heterogeneous Workflow v2 truthful READY proof — CURRENT;
|
||||
6. physical autofocus/optical applicability validation and dedicated calibrated real campaign;
|
||||
7. real Sparse SfM proof;
|
||||
8. durable Dense/OpenMVS orchestration;
|
||||
9. mesh / refinement / texturing / export;
|
||||
10. viewer foundation;
|
||||
11. offline Coverage Analysis scientific contract and implementation;
|
||||
12. multi-campaign registration / fusion;
|
||||
13. generic live acquisition adapter foundation;
|
||||
14. A6000 HDMI integration;
|
||||
15. S21 integration;
|
||||
16. live camera localization;
|
||||
17. live coverage overlay;
|
||||
18. actionable Capture Guidance;
|
||||
19. video ingestion / deterministic keyframes;
|
||||
20. final integration, UX, restart and performance proof;
|
||||
21. Product Definition v1 Definition-of-Done closure.
|
||||
5. Heterogeneous Workflow v2 truthful READY proof — PASS/FROZEN;
|
||||
6. Adaptive capture settings / generic autofocus foundation — CURRENT;
|
||||
7. physical autofocus/optical applicability validation and dedicated calibrated real campaign;
|
||||
8. real Sparse SfM proof;
|
||||
9. durable Dense/OpenMVS orchestration;
|
||||
10. mesh / refinement / texturing / export;
|
||||
11. viewer foundation;
|
||||
12. offline Coverage Analysis scientific contract and implementation;
|
||||
13. multi-campaign registration / fusion;
|
||||
14. generic live acquisition adapter foundation;
|
||||
15. A6000 HDMI integration;
|
||||
16. S21 integration;
|
||||
17. live camera localization;
|
||||
18. live coverage overlay;
|
||||
19. actionable Capture Guidance;
|
||||
20. video ingestion / deterministic keyframes;
|
||||
21. final integration, UX, restart and performance proof;
|
||||
22. Product Definition v1 Definition-of-Done closure.
|
||||
|
||||
## Adjustment rule
|
||||
|
||||
|
|
|
|||
|
|
@ -79,10 +79,12 @@ static Lardon3DCalibrationBootstrapV2Result db_result(Lardon3DProjectDbResult re
|
|||
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_DB_ERROR;
|
||||
}
|
||||
|
||||
Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
|
||||
static Lardon3DCalibrationBootstrapV2Result bootstrap_v2_publish(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char expected_artifact_sha256[32],
|
||||
Lardon3DCalibrationBootstrapV2Member *published_members,
|
||||
size_t member_capacity, bool attach_scope,
|
||||
Lardon3DCalibrationBootstrapV2Output *output) {
|
||||
if (!database || execution_id == 0 || !artifact || !expected_artifact_sha256 || !output ||
|
||||
artifact_size < BOOTSTRAP_V2_HEADER_SIZE ||
|
||||
|
|
@ -109,6 +111,8 @@ Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
|
|||
group_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_GROUPS || entry_count == 0 ||
|
||||
entry_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||
if (!attach_scope && (!published_members || member_capacity < entry_count))
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT;
|
||||
size_t expected_size = BOOTSTRAP_V2_HEADER_SIZE + (size_t)group_count * BOOTSTRAP_V2_GROUP_SIZE;
|
||||
if (entry_count > (SIZE_MAX - expected_size) / BOOTSTRAP_V2_ENTRY_SIZE)
|
||||
return LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT;
|
||||
|
|
@ -295,7 +299,7 @@ Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
|
|||
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
|
||||
}
|
||||
}
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK) {
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK && attach_scope) {
|
||||
db_status = lardon3d_project_db_assign_selected_calibration_scope(database, execution_id,
|
||||
scope.scope_id);
|
||||
if (db_status != LARDON3D_PROJECT_DB_OK) result = db_result(db_status);
|
||||
|
|
@ -305,8 +309,42 @@ Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
|
|||
output->scope = scope;
|
||||
output->calibration_count = entry_count;
|
||||
output->group_count = group_count;
|
||||
if (!attach_scope) {
|
||||
/* Item order, not artifact group order, is the durable composition key.
|
||||
* Workflow must bind it back to selected_execution_items.capture_id and
|
||||
* must never reverse-map Capture identity from image_id. */
|
||||
for (size_t index = 0; index < entry_count; ++index) {
|
||||
BootstrapV2Entry *entry = &entries[index];
|
||||
Lardon3DCalibrationBootstrapV2Member *member =
|
||||
&published_members[entry->selected_item_index];
|
||||
member->selected_item_index = entry->selected_item_index;
|
||||
member->image_id = entry->member.image_id;
|
||||
member->calibration_id = entry->member.calibration_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
free(entries);
|
||||
free(covered);
|
||||
return result;
|
||||
}
|
||||
|
||||
Lardon3DCalibrationBootstrapV2Result
|
||||
lardon3d_calibration_bootstrap_v2_publish_unattached(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char expected_artifact_sha256[32],
|
||||
Lardon3DCalibrationBootstrapV2Member *members, size_t member_capacity,
|
||||
Lardon3DCalibrationBootstrapV2Output *output) {
|
||||
return bootstrap_v2_publish(database, execution_id, artifact, artifact_size,
|
||||
expected_artifact_sha256, members,
|
||||
member_capacity, false, output);
|
||||
}
|
||||
|
||||
Lardon3DCalibrationBootstrapV2Result lardon3d_calibration_bootstrap_v2_import(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char expected_artifact_sha256[32],
|
||||
Lardon3DCalibrationBootstrapV2Output *output) {
|
||||
return bootstrap_v2_publish(database, execution_id, artifact, artifact_size,
|
||||
expected_artifact_sha256, NULL, 0, true, output);
|
||||
}
|
||||
|
|
|
|||
301
src/calibration_workflow_v2.c
Normal file
301
src/calibration_workflow_v2.c
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
#include <lardon3d/calibration_workflow_v2.h>
|
||||
|
||||
#include <lardon3d/optical_profiles.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static bool complete_state(const Lardon3DOpticalCaptureGeometricState *state) {
|
||||
return state->focus_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
|
||||
state->aperture_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
|
||||
state->stabilization != LARDON3D_OPTICAL_STABILIZATION_UNKNOWN &&
|
||||
state->crop_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
|
||||
state->pipeline_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
|
||||
state->representation_state == LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
|
||||
state->decoded_geometry_state ==
|
||||
LARDON3D_OPTICAL_OBSERVATION_OBSERVED &&
|
||||
state->decoded_width != 0 && state->decoded_height != 0;
|
||||
}
|
||||
|
||||
static bool same_state(const Lardon3DOpticalCaptureGeometricState *left,
|
||||
const Lardon3DOpticalCaptureGeometricState *right) {
|
||||
return left->optical_configuration_id == right->optical_configuration_id &&
|
||||
left->state_version == right->state_version &&
|
||||
left->provenance == right->provenance &&
|
||||
left->focus_state == right->focus_state &&
|
||||
strcmp(left->focus_observation, right->focus_observation) == 0 &&
|
||||
left->aperture_state == right->aperture_state &&
|
||||
left->aperture_x1000 == right->aperture_x1000 &&
|
||||
left->stabilization == right->stabilization &&
|
||||
left->crop_state == right->crop_state &&
|
||||
strcmp(left->crop_observation, right->crop_observation) == 0 &&
|
||||
left->pipeline_state == right->pipeline_state &&
|
||||
strcmp(left->pipeline_observation, right->pipeline_observation) == 0 &&
|
||||
left->representation_state == right->representation_state &&
|
||||
strcmp(left->representation_observation,
|
||||
right->representation_observation) == 0 &&
|
||||
left->decoded_geometry_state == right->decoded_geometry_state &&
|
||||
left->decoded_width == right->decoded_width &&
|
||||
left->decoded_height == right->decoded_height;
|
||||
}
|
||||
|
||||
static Lardon3DCalibrationWorkflowV2Result
|
||||
bootstrap_result(Lardon3DCalibrationBootstrapV2Result result) {
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_MALFORMED_ARTIFACT ||
|
||||
result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_PROVENANCE_MISMATCH)
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_EVIDENCE;
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_SELECTION_CONFLICT)
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OUT_OF_MEMORY)
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_OUT_OF_MEMORY;
|
||||
if (result == LARDON3D_CALIBRATION_BOOTSTRAP_V2_INVALID_ARGUMENT)
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT;
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR;
|
||||
}
|
||||
|
||||
static Lardon3DCalibrationWorkflowV2Result
|
||||
db_error(Lardon3DProjectDbResult result) {
|
||||
return result == LARDON3D_PROJECT_DB_CONSTRAINT ||
|
||||
result == LARDON3D_PROJECT_DB_NOT_FOUND
|
||||
? LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT
|
||||
: LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR;
|
||||
}
|
||||
|
||||
static void hex_digest(const unsigned char digest[32], char output[65]) {
|
||||
static const char digits[] = "0123456789abcdef";
|
||||
for (size_t index = 0; index < 32; ++index) {
|
||||
output[2 * index] = digits[digest[index] >> 4];
|
||||
output[2 * index + 1] = digits[digest[index] & 15];
|
||||
}
|
||||
output[64] = '\0';
|
||||
}
|
||||
|
||||
static Lardon3DCalibrationWorkflowV2Result
|
||||
publish_applicability(Lardon3DProjectDb *database,
|
||||
const Lardon3DCalibrationWorkflowV2Binding *binding,
|
||||
uint64_t calibration_id, uint64_t configuration_id,
|
||||
uint64_t *applicability_id) {
|
||||
Lardon3DSparseCalibration calibration;
|
||||
Lardon3DProjectDbResult status =
|
||||
lardon3d_sparse_calibration_load(database, calibration_id, &calibration);
|
||||
if (status != LARDON3D_PROJECT_DB_OK)
|
||||
return db_error(status);
|
||||
char scientific_hash[65];
|
||||
char provenance_hash[65];
|
||||
hex_digest(calibration.scientific_hash, scientific_hash);
|
||||
hex_digest(calibration.provenance_fingerprint, provenance_hash);
|
||||
Lardon3DOpticalCalibrationProfile requested = {0};
|
||||
requested.optical_configuration_id = configuration_id;
|
||||
requested.sparse_calibration_id = calibration_id;
|
||||
requested.profile_version =
|
||||
LARDON3D_CALIBRATION_BOOTSTRAP_V2_ARTIFACT_VERSION;
|
||||
requested.applicability = LARDON3D_OPTICAL_CALIBRATION_EXACT_CONFIGURATION;
|
||||
requested.created_at = 0;
|
||||
(void)snprintf(requested.name, sizeof(requested.name), "L3DCALB2-%s",
|
||||
scientific_hash);
|
||||
(void)snprintf(requested.provenance, sizeof(requested.provenance),
|
||||
"L3DCALB2 group-sha256=%s", provenance_hash);
|
||||
Lardon3DOpticalCalibrationProfile profile;
|
||||
status = lardon3d_optical_calibration_profile_create(database, &requested,
|
||||
&profile);
|
||||
if (status != LARDON3D_PROJECT_DB_OK)
|
||||
return db_error(status);
|
||||
Lardon3DOpticalCalibrationApplicabilityV2 applicability;
|
||||
status = lardon3d_optical_calibration_applicability_v2_create(
|
||||
database, profile.calibration_profile_id, binding->exemplar_capture_id,
|
||||
&applicability);
|
||||
if (status != LARDON3D_PROJECT_DB_OK)
|
||||
return db_error(status);
|
||||
*applicability_id = applicability.applicability_id;
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
|
||||
}
|
||||
|
||||
Lardon3DCalibrationWorkflowV2Result lardon3d_calibration_workflow_v2_complete(
|
||||
Lardon3DProjectDb *database, uint64_t execution_id,
|
||||
const unsigned char *artifact, size_t artifact_size,
|
||||
const unsigned char expected_artifact_sha256[32],
|
||||
const Lardon3DCalibrationWorkflowV2Binding *bindings, size_t binding_count,
|
||||
Lardon3DCalibrationWorkflowV2Output *output) {
|
||||
if (output)
|
||||
memset(output, 0, sizeof(*output));
|
||||
if (!database || execution_id == 0 || !artifact ||
|
||||
!expected_artifact_sha256 || !bindings || !output || binding_count == 0 ||
|
||||
binding_count > LARDON3D_CALIBRATION_BOOTSTRAP_V2_MAX_ENTRIES)
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT;
|
||||
|
||||
Lardon3DProjectDbSelectedExecution execution;
|
||||
Lardon3DProjectDbResult status = lardon3d_project_db_load_selected_execution(
|
||||
database, execution_id, &execution);
|
||||
if (status != LARDON3D_PROJECT_DB_OK)
|
||||
return db_error(status);
|
||||
if (execution.item_count != binding_count ||
|
||||
(execution.stage != LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
|
||||
execution.stage != LARDON3D_SELECTED_EXECUTION_READY))
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
|
||||
|
||||
const Lardon3DCalibrationWorkflowV2Binding **ordered =
|
||||
calloc(binding_count, sizeof(*ordered));
|
||||
Lardon3DOpticalCaptureGeometricState *states =
|
||||
calloc(binding_count, sizeof(*states));
|
||||
Lardon3DCalibrationBootstrapV2Member *members =
|
||||
calloc(binding_count, sizeof(*members));
|
||||
if (!ordered || !states || !members) {
|
||||
free(ordered);
|
||||
free(states);
|
||||
free(members);
|
||||
return LARDON3D_CALIBRATION_WORKFLOW_V2_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
Lardon3DCalibrationWorkflowV2Result result =
|
||||
LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
|
||||
for (size_t index = 0; index < binding_count; ++index) {
|
||||
const Lardon3DCalibrationWorkflowV2Binding *binding = &bindings[index];
|
||||
if (binding->selected_item_index >= binding_count ||
|
||||
ordered[binding->selected_item_index] || binding->capture_id == 0 ||
|
||||
(binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC &&
|
||||
(binding->applicability_id != 0 ||
|
||||
binding->exemplar_capture_id != 0)) ||
|
||||
(binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_EXISTING_EXPLICIT &&
|
||||
(binding->applicability_id == 0 ||
|
||||
binding->exemplar_capture_id != 0)) ||
|
||||
(binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT &&
|
||||
(binding->applicability_id != 0 ||
|
||||
binding->exemplar_capture_id == 0)) ||
|
||||
binding->kind < LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC ||
|
||||
binding->kind > LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT) {
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_ARGUMENT;
|
||||
break;
|
||||
}
|
||||
ordered[binding->selected_item_index] = binding;
|
||||
}
|
||||
|
||||
/* Preflight all scientific Capture bindings before immutable publication.
|
||||
* A missing/incomplete tuple is a truthful semantic state, while a caller
|
||||
* mapping that disagrees with selected_execution_items is an error. */
|
||||
for (size_t index = 0; index < binding_count &&
|
||||
result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
|
||||
++index) {
|
||||
const Lardon3DCalibrationWorkflowV2Binding *binding = ordered[index];
|
||||
Lardon3DProjectDbSelectedExecutionItem selected;
|
||||
status = lardon3d_project_db_load_selected_execution_item(
|
||||
database, execution_id, (uint32_t)index, &selected);
|
||||
if (status != LARDON3D_PROJECT_DB_OK || !selected.has_image ||
|
||||
selected.capture_id != binding->capture_id) {
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
|
||||
break;
|
||||
}
|
||||
status = lardon3d_optical_capture_geometric_state_load(
|
||||
database, binding->capture_id, &states[index]);
|
||||
if (status == LARDON3D_PROJECT_DB_NOT_FOUND ||
|
||||
(status == LARDON3D_PROJECT_DB_OK && !complete_state(&states[index]))) {
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
|
||||
break;
|
||||
}
|
||||
if (status != LARDON3D_PROJECT_DB_OK) {
|
||||
result = db_error(status);
|
||||
break;
|
||||
}
|
||||
if (binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT) {
|
||||
Lardon3DOpticalCaptureGeometricState exemplar;
|
||||
status = lardon3d_optical_capture_geometric_state_load(
|
||||
database, binding->exemplar_capture_id, &exemplar);
|
||||
if (status == LARDON3D_PROJECT_DB_NOT_FOUND ||
|
||||
(status == LARDON3D_PROJECT_DB_OK && !complete_state(&exemplar))) {
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
|
||||
break;
|
||||
}
|
||||
if (status != LARDON3D_PROJECT_DB_OK) {
|
||||
result = db_error(status);
|
||||
break;
|
||||
}
|
||||
if (!same_state(&states[index], &exemplar)) {
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC) {
|
||||
Lardon3DOpticalCalibrationResolutionV2 resolution;
|
||||
status = lardon3d_optical_capture_calibration_resolve_v2(
|
||||
database, binding->capture_id, &resolution);
|
||||
if (status != LARDON3D_PROJECT_DB_OK) {
|
||||
result = db_error(status);
|
||||
break;
|
||||
}
|
||||
if (resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED)
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
|
||||
else if (resolution.kind ==
|
||||
LARDON3D_OPTICAL_CALIBRATION_SELECTION_REQUIRED)
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED;
|
||||
}
|
||||
}
|
||||
|
||||
Lardon3DCalibrationBootstrapV2Output publication;
|
||||
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
|
||||
Lardon3DCalibrationBootstrapV2Result published =
|
||||
lardon3d_calibration_bootstrap_v2_publish_unattached(
|
||||
database, execution_id, artifact, artifact_size,
|
||||
expected_artifact_sha256, members, binding_count, &publication);
|
||||
if (published != LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK)
|
||||
result = bootstrap_result(published);
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < binding_count &&
|
||||
result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY;
|
||||
++index) {
|
||||
const Lardon3DCalibrationWorkflowV2Binding *binding = ordered[index];
|
||||
uint64_t applicability_id = binding->applicability_id;
|
||||
if (binding->kind == LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT)
|
||||
result = publish_applicability(
|
||||
database, binding, members[index].calibration_id,
|
||||
states[index].optical_configuration_id, &applicability_id);
|
||||
if (result != LARDON3D_CALIBRATION_WORKFLOW_V2_READY)
|
||||
break;
|
||||
if (binding->kind != LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC) {
|
||||
status = lardon3d_optical_capture_calibration_select_v2(
|
||||
database, binding->capture_id, applicability_id);
|
||||
if (status != LARDON3D_PROJECT_DB_OK) {
|
||||
result = db_error(status);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Lardon3DOpticalCalibrationResolutionV2 resolution;
|
||||
status = lardon3d_optical_capture_calibration_resolve_v2(
|
||||
database, binding->capture_id, &resolution);
|
||||
if (status != LARDON3D_PROJECT_DB_OK) {
|
||||
result = db_error(status);
|
||||
break;
|
||||
}
|
||||
if (resolution.kind == LARDON3D_OPTICAL_CALIBRATION_REQUIRED)
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED;
|
||||
else if (resolution.kind == LARDON3D_OPTICAL_CALIBRATION_SELECTION_REQUIRED)
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED;
|
||||
else if (resolution.sparse_calibration_id != members[index].calibration_id)
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT;
|
||||
}
|
||||
|
||||
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
|
||||
status = lardon3d_project_db_assign_selected_calibration_scope(
|
||||
database, execution_id, publication.scope.scope_id);
|
||||
if (status != LARDON3D_PROJECT_DB_OK)
|
||||
result = db_error(status);
|
||||
}
|
||||
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
|
||||
status = lardon3d_project_db_load_selected_execution(database, execution_id,
|
||||
&execution);
|
||||
if (status != LARDON3D_PROJECT_DB_OK ||
|
||||
execution.stage != LARDON3D_SELECTED_EXECUTION_READY ||
|
||||
!execution.has_calibration_scope ||
|
||||
execution.calibration_scope_id != publication.scope.scope_id)
|
||||
result = LARDON3D_CALIBRATION_WORKFLOW_V2_DB_ERROR;
|
||||
}
|
||||
if (result == LARDON3D_CALIBRATION_WORKFLOW_V2_READY) {
|
||||
output->publication = publication;
|
||||
output->selected_item_count = (uint32_t)binding_count;
|
||||
}
|
||||
free(ordered);
|
||||
free(states);
|
||||
free(members);
|
||||
return result;
|
||||
}
|
||||
342
tests/test_calibration_workflow_v2.c
Normal file
342
tests/test_calibration_workflow_v2.c
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
#include <lardon3d/calibration_tooling_v2.h>
|
||||
#include <lardon3d/calibration_workflow_v2.h>
|
||||
#include <lardon3d/optical_profiles.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__, \
|
||||
#condition); \
|
||||
return false; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static bool sql(const char *path, const char *text) {
|
||||
sqlite3 *database = NULL;
|
||||
char *error = NULL;
|
||||
if (sqlite3_open(path, &database) != SQLITE_OK)
|
||||
return false;
|
||||
int code = sqlite3_exec(database, text, NULL, NULL, &error);
|
||||
if (code != SQLITE_OK)
|
||||
fprintf(stderr, "SQL: %s\n", error ? error : "error");
|
||||
sqlite3_free(error);
|
||||
sqlite3_close(database);
|
||||
return code == SQLITE_OK;
|
||||
}
|
||||
|
||||
static void digest(unsigned char output[32], unsigned char seed) {
|
||||
for (size_t index = 0; index < 32; ++index)
|
||||
output[index] = (unsigned char)(seed + index);
|
||||
}
|
||||
|
||||
static void evidence(Lardon3DCalibrationToolingV2Evidence *value,
|
||||
Lardon3DCalibrationToolingV2Group groups[2],
|
||||
Lardon3DCalibrationToolingV2Entry entries[2]) {
|
||||
memset(value, 0, sizeof(*value));
|
||||
memset(groups, 0, 2 * sizeof(*groups));
|
||||
memset(entries, 0, 2 * sizeof(*entries));
|
||||
for (size_t index = 0; index < 2; ++index) {
|
||||
digest(groups[index].group_identity_sha256, (unsigned char)(1 + index));
|
||||
groups[index].group_version = 1;
|
||||
digest(groups[index].optical_state_sha256, (unsigned char)(11 + index));
|
||||
digest(groups[index].target_sha256, (unsigned char)(21 + index));
|
||||
digest(groups[index].solver_executable_sha256, (unsigned char)(31 + index));
|
||||
digest(groups[index].solver_configuration_sha256,
|
||||
(unsigned char)(41 + index));
|
||||
digest(groups[index].initialization_evidence_sha256,
|
||||
(unsigned char)(51 + index));
|
||||
digest(groups[index].validation_evidence_sha256,
|
||||
(unsigned char)(61 + index));
|
||||
groups[index].entries = &entries[index];
|
||||
groups[index].entry_count = 1;
|
||||
entries[index].selected_item_index = (uint32_t)index;
|
||||
entries[index].image_id = index + 1;
|
||||
memset(entries[index].representation_sha256, (int)(0x11 * (index + 1)), 32);
|
||||
entries[index].width = index == 0 ? 4000 : 6000;
|
||||
entries[index].height = index == 0 ? 3000 : 4000;
|
||||
entries[index].fx = index == 0 ? 3000.0 : 4500.0;
|
||||
entries[index].fy = entries[index].fx + 1.0;
|
||||
entries[index].cx = entries[index].width / 2.0;
|
||||
entries[index].cy = entries[index].height / 2.0;
|
||||
entries[index].support_images = 20;
|
||||
entries[index].support_observations = 1000;
|
||||
entries[index].reprojection_rmse_px = 0.4;
|
||||
entries[index].maximum_parameter_delta = 0.01;
|
||||
entries[index].validation_flags =
|
||||
LARDON3D_CALIBRATION_TOOLING_V2_VALIDATION_FLAGS;
|
||||
}
|
||||
value->groups = groups;
|
||||
value->group_count = 2;
|
||||
value->entry_count = 2;
|
||||
}
|
||||
|
||||
static bool create_project(const char *path, Lardon3DProjectDb **database,
|
||||
uint64_t *execution_id) {
|
||||
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY];
|
||||
CHECK(lardon3d_project_db_open(path, database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
lardon3d_project_db_close(*database);
|
||||
*database = NULL;
|
||||
CHECK(sql(
|
||||
path,
|
||||
"INSERT INTO scansets VALUES(1,'workflow-v2',1,1);"
|
||||
"INSERT INTO tasks "
|
||||
"VALUES(1,'quality','photo_quality.triage',1,5,5,1,1,0,0,0,0,1);"
|
||||
"INSERT INTO tasks "
|
||||
"VALUES(2,'campaign','acquisition_campaign.run',1,5,5,1,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,1,1,1,1,1,1,0,0,1,1,0,'GOOD'),"
|
||||
"(1,2,1,1,0,1,0,1,1,1,1,1,1,0,0,1,1,0,'GOOD');"
|
||||
"INSERT INTO acquisition_campaign_tasks VALUES(2,1,2,2,X'02');"
|
||||
"INSERT INTO captures VALUES(1,1,1),(2,1,2);"
|
||||
"INSERT INTO acquisition_campaign_captures VALUES(2,11,1),(2,12,2);"
|
||||
"INSERT INTO image_assets VALUES"
|
||||
"(1,X'1111111111111111111111111111111111111111111111111111111111111111',"
|
||||
"'assets/images/11/"
|
||||
"1111111111111111111111111111111111111111111111111111111111111111',1,1,1)"
|
||||
","
|
||||
"(2,X'2222222222222222222222222222222222222222222222222222222222222222',"
|
||||
"'assets/images/22/"
|
||||
"2222222222222222222222222222222222222222222222222222222222222222',1,1,2)"
|
||||
";"
|
||||
"INSERT INTO images VALUES(1,1,1,'a','a',NULL,1),(2,1,2,'b','b',NULL,2);"
|
||||
"INSERT INTO capture_images VALUES(1,1),(2,2);"
|
||||
"INSERT INTO camera_body_profiles VALUES(1,'Maker','Body','Body');"
|
||||
"INSERT INTO lens_profiles VALUES(1,'Maker','L1','L1',2,2,24000,24000);"
|
||||
"INSERT INTO lens_profiles VALUES(2,'Maker','L2','L2',2,2,50000,50000);"
|
||||
"INSERT INTO optical_configurations VALUES(1,1,1,24000),(2,1,2,50000);"
|
||||
"INSERT INTO capture_optical_configurations "
|
||||
"VALUES(1,1,2,NULL,NULL),(2,2,2,NULL,NULL);"));
|
||||
CHECK(lardon3d_project_db_open(path, database, error) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DProjectDbSelectedExecutionItem items[2] = {
|
||||
{.item_index = 0,
|
||||
.quality_group_id = 1,
|
||||
.campaign_group_id = 11,
|
||||
.capture_id = 1,
|
||||
.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE},
|
||||
{.item_index = 1,
|
||||
.quality_group_id = 2,
|
||||
.campaign_group_id = 12,
|
||||
.capture_id = 2,
|
||||
.representation_source = LARDON3D_SELECTED_REPRESENTATION_SOURCE_IMAGE}};
|
||||
Lardon3DProjectDbSelectedExecution execution;
|
||||
CHECK(lardon3d_project_db_create_selected_execution(*database, 1, 2, items, 2,
|
||||
1, &execution) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_selected_representation(
|
||||
*database, execution.execution_id, 0, 1, 1) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_project_db_record_selected_representation(
|
||||
*database, execution.execution_id, 1, 2, 2) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
*execution_id = execution.execution_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool add_state(Lardon3DProjectDb *database, uint64_t capture_id,
|
||||
uint64_t configuration_id, uint32_t width,
|
||||
uint32_t height) {
|
||||
Lardon3DOpticalCaptureGeometricState input = {
|
||||
.capture_id = capture_id,
|
||||
.optical_configuration_id = configuration_id,
|
||||
.state_version = 1,
|
||||
.provenance = LARDON3D_OPTICAL_GEOMETRIC_STATE_CALLER_EXPLICIT,
|
||||
.focus_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
|
||||
.aperture_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
|
||||
.aperture_x1000 = 8000,
|
||||
.stabilization = LARDON3D_OPTICAL_STABILIZATION_OFF,
|
||||
.crop_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
|
||||
.pipeline_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
|
||||
.representation_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
|
||||
.decoded_geometry_state = LARDON3D_OPTICAL_OBSERVATION_OBSERVED,
|
||||
.decoded_width = width,
|
||||
.decoded_height = height};
|
||||
strcpy(input.focus_observation, "fixed");
|
||||
strcpy(input.crop_observation, "full");
|
||||
strcpy(input.pipeline_observation, "raw-v1");
|
||||
strcpy(input.representation_observation, "png-v1");
|
||||
Lardon3DOpticalCaptureGeometricState output;
|
||||
return lardon3d_optical_capture_geometric_state_create(
|
||||
database, &input, &output) == LARDON3D_PROJECT_DB_OK;
|
||||
}
|
||||
|
||||
static bool is_unattached(Lardon3DProjectDb *database, uint64_t execution_id) {
|
||||
Lardon3DProjectDbSelectedExecution execution;
|
||||
return lardon3d_project_db_load_selected_execution(
|
||||
database, execution_id, &execution) == LARDON3D_PROJECT_DB_OK &&
|
||||
execution.stage == LARDON3D_SELECTED_EXECUTION_CALIBRATION &&
|
||||
!execution.has_calibration_scope;
|
||||
}
|
||||
|
||||
static bool run_cases(void) {
|
||||
Lardon3DCalibrationToolingV2Evidence source;
|
||||
Lardon3DCalibrationToolingV2Group groups[2];
|
||||
Lardon3DCalibrationToolingV2Entry entries[2];
|
||||
evidence(&source, groups, entries);
|
||||
unsigned char artifact[1024], artifact_hash[32];
|
||||
size_t artifact_size = 0;
|
||||
CHECK(lardon3d_calibration_tooling_v2_produce(
|
||||
&source, artifact, sizeof(artifact), &artifact_size,
|
||||
artifact_hash) == LARDON3D_CALIBRATION_TOOLING_V2_OK);
|
||||
|
||||
char directory[] = "/tmp/lardon3d-workflow-v2-XXXXXX";
|
||||
CHECK(mkdtemp(directory) != NULL);
|
||||
char path[512];
|
||||
CHECK(snprintf(path, sizeof(path), "%s/project.db", directory) > 0);
|
||||
Lardon3DProjectDb *database = NULL;
|
||||
uint64_t execution_id = 0;
|
||||
CHECK(create_project(path, &database, &execution_id));
|
||||
Lardon3DCalibrationWorkflowV2Binding bindings[2] = {
|
||||
{.selected_item_index = 0,
|
||||
.capture_id = 1,
|
||||
.kind = LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT,
|
||||
.exemplar_capture_id = 1},
|
||||
{.selected_item_index = 1,
|
||||
.capture_id = 2,
|
||||
.kind = LARDON3D_CALIBRATION_WORKFLOW_V2_PUBLISH_EXPLICIT,
|
||||
.exemplar_capture_id = 2}};
|
||||
Lardon3DCalibrationWorkflowV2Output output;
|
||||
|
||||
/* No complete observed tuple is a semantic non-ready state and must not
|
||||
* trigger the unattached publication primitive. */
|
||||
CHECK(lardon3d_calibration_workflow_v2_complete(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
bindings, 2,
|
||||
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_CALIBRATION_REQUIRED);
|
||||
CHECK(is_unattached(database, execution_id));
|
||||
CHECK(add_state(database, 1, 1, 4000, 3000));
|
||||
CHECK(add_state(database, 2, 2, 6000, 4000));
|
||||
|
||||
unsigned char wrong_hash[32] = {0};
|
||||
memset(&output, 0xa5, sizeof(output));
|
||||
CHECK(lardon3d_calibration_workflow_v2_complete(
|
||||
database, execution_id, artifact, artifact_size, wrong_hash,
|
||||
bindings, 2,
|
||||
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_INVALID_EVIDENCE);
|
||||
const Lardon3DCalibrationWorkflowV2Output empty_output = {0};
|
||||
CHECK(memcmp(&output, &empty_output, sizeof(output)) == 0);
|
||||
CHECK(is_unattached(database, execution_id));
|
||||
|
||||
Lardon3DCalibrationBootstrapV2Member probe_members[2];
|
||||
Lardon3DCalibrationBootstrapV2Output probe_publication;
|
||||
Lardon3DCalibrationBootstrapV2Result probe =
|
||||
lardon3d_calibration_bootstrap_v2_publish_unattached(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
probe_members, 2, &probe_publication);
|
||||
if (probe != LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK)
|
||||
fprintf(stderr, "unattached probe result=%d\n", (int)probe);
|
||||
CHECK(probe == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK);
|
||||
|
||||
Lardon3DCalibrationWorkflowV2Result completed =
|
||||
lardon3d_calibration_workflow_v2_complete(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
bindings, 2, &output);
|
||||
if (completed != LARDON3D_CALIBRATION_WORKFLOW_V2_READY)
|
||||
fprintf(stderr, "happy workflow result=%d\n", (int)completed);
|
||||
CHECK(completed == LARDON3D_CALIBRATION_WORKFLOW_V2_READY);
|
||||
uint64_t scope_id = output.publication.scope.scope_id;
|
||||
Lardon3DSparseCalibrationMember members[2];
|
||||
size_t count = 0;
|
||||
uint64_t next = 0;
|
||||
CHECK(lardon3d_sparse_calibration_scope_list_members(
|
||||
database, scope_id, 0, members, 2, &count, &next) ==
|
||||
LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(count == 2 && members[0].calibration_id != members[1].calibration_id);
|
||||
CHECK(lardon3d_calibration_workflow_v2_complete(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
bindings, 2, &output) == LARDON3D_CALIBRATION_WORKFLOW_V2_READY &&
|
||||
output.publication.scope.scope_id == scope_id);
|
||||
lardon3d_project_db_close(database);
|
||||
|
||||
/* A requested applicability for the other heterogeneous member is exact-
|
||||
* state incompatible and cannot attach the otherwise complete scope. */
|
||||
char wrong_path[512];
|
||||
CHECK(snprintf(wrong_path, sizeof(wrong_path), "%s/wrong.db", directory) > 0);
|
||||
CHECK(create_project(wrong_path, &database, &execution_id));
|
||||
CHECK(add_state(database, 1, 1, 4000, 3000));
|
||||
CHECK(add_state(database, 2, 2, 6000, 4000));
|
||||
Lardon3DCalibrationBootstrapV2Member published[2];
|
||||
Lardon3DCalibrationBootstrapV2Output publication;
|
||||
CHECK(lardon3d_calibration_bootstrap_v2_publish_unattached(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
published, 2,
|
||||
&publication) == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK);
|
||||
Lardon3DOpticalCalibrationProfile profile = {
|
||||
.optical_configuration_id = 2,
|
||||
.sparse_calibration_id = published[1].calibration_id,
|
||||
.profile_version = 1,
|
||||
.applicability = LARDON3D_OPTICAL_CALIBRATION_EXACT_CONFIGURATION};
|
||||
strcpy(profile.name, "wrong-member");
|
||||
strcpy(profile.provenance, "test");
|
||||
Lardon3DOpticalCalibrationProfile stored_profile;
|
||||
CHECK(lardon3d_optical_calibration_profile_create(
|
||||
database, &profile, &stored_profile) == LARDON3D_PROJECT_DB_OK);
|
||||
Lardon3DOpticalCalibrationApplicabilityV2 applicability;
|
||||
CHECK(lardon3d_optical_calibration_applicability_v2_create(
|
||||
database, stored_profile.calibration_profile_id, 2,
|
||||
&applicability) == LARDON3D_PROJECT_DB_OK);
|
||||
bindings[0].kind = LARDON3D_CALIBRATION_WORKFLOW_V2_EXISTING_EXPLICIT;
|
||||
bindings[0].applicability_id = applicability.applicability_id;
|
||||
bindings[0].exemplar_capture_id = 0;
|
||||
CHECK(lardon3d_calibration_workflow_v2_complete(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
bindings, 2,
|
||||
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_ASSIGNMENT_CONFLICT);
|
||||
CHECK(is_unattached(database, execution_id));
|
||||
lardon3d_project_db_close(database);
|
||||
|
||||
/* Two exact candidates remain visibly ambiguous in AUTOMATIC mode even
|
||||
* when both happen to reference the artifact's immutable calibration. */
|
||||
char ambiguous_path[512];
|
||||
CHECK(snprintf(ambiguous_path, sizeof(ambiguous_path), "%s/ambiguous.db",
|
||||
directory) > 0);
|
||||
CHECK(create_project(ambiguous_path, &database, &execution_id));
|
||||
CHECK(add_state(database, 1, 1, 4000, 3000));
|
||||
CHECK(add_state(database, 2, 2, 6000, 4000));
|
||||
CHECK(lardon3d_calibration_bootstrap_v2_publish_unattached(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
published, 2,
|
||||
&publication) == LARDON3D_CALIBRATION_BOOTSTRAP_V2_OK);
|
||||
for (size_t index = 0; index < 2; ++index) {
|
||||
memset(&profile, 0, sizeof(profile));
|
||||
profile.optical_configuration_id = 1;
|
||||
profile.sparse_calibration_id = published[0].calibration_id;
|
||||
profile.profile_version = 1;
|
||||
profile.applicability = LARDON3D_OPTICAL_CALIBRATION_EXACT_CONFIGURATION;
|
||||
profile.created_at = 0;
|
||||
CHECK(snprintf(profile.name, sizeof(profile.name), "ambiguous-%zu", index) >
|
||||
0);
|
||||
strcpy(profile.provenance, "test ambiguity");
|
||||
CHECK(lardon3d_optical_calibration_profile_create(
|
||||
database, &profile, &stored_profile) == LARDON3D_PROJECT_DB_OK);
|
||||
CHECK(lardon3d_optical_calibration_applicability_v2_create(
|
||||
database, stored_profile.calibration_profile_id, 1,
|
||||
&applicability) == LARDON3D_PROJECT_DB_OK);
|
||||
}
|
||||
bindings[0].kind = LARDON3D_CALIBRATION_WORKFLOW_V2_AUTOMATIC;
|
||||
bindings[0].applicability_id = 0;
|
||||
bindings[0].exemplar_capture_id = 0;
|
||||
CHECK(lardon3d_calibration_workflow_v2_complete(
|
||||
database, execution_id, artifact, artifact_size, artifact_hash,
|
||||
bindings, 2,
|
||||
&output) == LARDON3D_CALIBRATION_WORKFLOW_V2_SELECTION_REQUIRED);
|
||||
CHECK(is_unattached(database, execution_id));
|
||||
lardon3d_project_db_close(database);
|
||||
|
||||
CHECK(unlink(path) == 0);
|
||||
CHECK(unlink(wrong_path) == 0);
|
||||
CHECK(unlink(ambiguous_path) == 0);
|
||||
CHECK(rmdir(directory) == 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(void) { return run_cases() ? EXIT_SUCCESS : EXIT_FAILURE; }
|
||||
Loading…
Reference in a new issue