lardon3d/tests/test_calibration_workflow_v2.c

342 lines
16 KiB
C

#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; }