#include #include #include #include #include #include #include #include #include #define CHECK(value) \ do { \ if (!(value)) { \ fprintf(stderr, "Sparse SfM check failed at line %d: %s\n", __LINE__, \ #value); \ return false; \ } \ } while (0) static void test_put_u32(unsigned char *record, size_t *offset, uint32_t value) { for (unsigned byte = 0; byte < 4; ++byte) record[(*offset)++] = (unsigned char)(value >> (byte * 8)); } static void test_put_u64(unsigned char *record, size_t *offset, uint64_t value) { for (unsigned byte = 0; byte < 8; ++byte) record[(*offset)++] = (unsigned char)(value >> (byte * 8)); } static bool test_h_identity(Lardon3DIncrementalReconstructionMetadata *metadata) { unsigned char record[76] = {0}; unsigned int size = 0; size_t offset = 8; memcpy(record, "L3DHIDV1", 8); test_put_u32(record, &offset, 1); test_put_u64(record, &offset, metadata->base_reconstruction_id); test_put_u64(record, &offset, metadata->extension_track_set_id); test_put_u64(record, &offset, metadata->calibration_scope_id); test_put_u32(record, &offset, metadata->incremental_kind); test_put_u32(record, &offset, metadata->incremental_version); memcpy(record + offset, metadata->parameter_fingerprint, 32); offset += 32; return offset == sizeof(record) && EVP_Digest(record, sizeof(record), metadata->scientific_identity, &size, EVP_sha256(), NULL) == 1 && size == sizeof(metadata->scientific_identity); } static int raw_exec(const char *path, const char *sql) { sqlite3 *connection = NULL; char *message = NULL; int code = sqlite3_open_v2(path, &connection, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); if (code == SQLITE_OK) code = sqlite3_exec(connection, "PRAGMA foreign_keys=ON", NULL, NULL, &message); if (code == SQLITE_OK) code = sqlite3_exec(connection, sql, NULL, NULL, &message); sqlite3_free(message); if (connection) sqlite3_close(connection); return code; } static bool raw_constraint(const char *path, const char *sql) { return raw_exec(path, sql) == SQLITE_CONSTRAINT; } static bool raw_scalar_equals(const char *path, const char *sql, sqlite3_int64 expected) { sqlite3 *connection = NULL; sqlite3_stmt *statement = NULL; bool matches = false; if (sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) == SQLITE_OK && sqlite3_prepare_v2(connection, sql, -1, &statement, NULL) == SQLITE_OK && sqlite3_step(statement) == SQLITE_ROW) matches = sqlite3_column_int64(statement, 0) == expected; if (statement) sqlite3_finalize(statement); if (connection) sqlite3_close(connection); return matches; } static bool register_image(Lardon3DProjectDb *db, uint64_t scanset_id, unsigned char seed, uint64_t *image_id) { unsigned char hash[32] = {0}; char asset_path[128]; char hex[65]; Lardon3DProjectDbImage image; Lardon3DProjectDbImageRegisterStatus status; hash[0] = seed; static const char digits[] = "0123456789abcdef"; for (size_t index = 0; index < 32; ++index) { hex[index * 2] = digits[hash[index] >> 4]; hex[index * 2 + 1] = digits[hash[index] & 15U]; } hex[64] = '\0'; (void)snprintf(asset_path, sizeof(asset_path), "assets/images/%c%c/%s", hex[0], hex[1], hex); Lardon3DProjectDbResult result = lardon3d_project_db_register_image( db, scanset_id, hash, asset_path, 1, "test.jpg", "/test.jpg", 0, 1, &status, &image); if (result != LARDON3D_PROJECT_DB_OK) { char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; (void)lardon3d_project_db_last_error(db, error); fprintf(stderr, "register image: %s (%d)\n", error, (int)result); } return result == LARDON3D_PROJECT_DB_OK && (*image_id = image.image_id) != 0; } static bool run_test(void) { char path[] = "/tmp/lardon3d-sparse-model-XXXXXX"; int descriptor = mkstemp(path); CHECK(descriptor >= 0); close(descriptor); unlink(path); Lardon3DProjectDb *db = NULL; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_schema_version(db) == 18); Lardon3DProjectDbScanSet scanset; CHECK(lardon3d_project_db_create_scanset(db, "Sparse model", &scanset) == LARDON3D_PROJECT_DB_OK); uint64_t image_a = 0; uint64_t image_b = 0; CHECK(register_image(db, scanset.scanset_id, 1, &image_a)); CHECK(register_image(db, scanset.scanset_id, 2, &image_b)); Lardon3DSparseCalibration calibration = {0}; calibration.model_kind = LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE; calibration.model_version = LARDON3D_SPARSE_SFM_CALIBRATION_VERSION; calibration.width = 4000; calibration.height = 3000; calibration.fx = 2000.0; calibration.fy = 2001.0; calibration.cx = 2000.0; calibration.cy = 1500.0; calibration.provenance_kind = LARDON3D_SPARSE_SFM_PROVENANCE_USER_EXPLICIT; Lardon3DSparseCalibration stored; CHECK(lardon3d_sparse_calibration_create(db, &calibration, &stored) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibration alternate_calibration = calibration; alternate_calibration.provenance_fingerprint[0] = 77; Lardon3DSparseCalibration alternate_stored; CHECK(lardon3d_sparse_calibration_create( db, &alternate_calibration, &alternate_stored) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibration duplicate; CHECK(lardon3d_sparse_calibration_create(db, &calibration, &duplicate) == LARDON3D_PROJECT_DB_OK); CHECK(stored.calibration_id == duplicate.calibration_id); Lardon3DSparseCalibrationMember members[2] = { {.image_id = image_b, .calibration_id = stored.calibration_id}, {.image_id = image_a, .calibration_id = stored.calibration_id}, }; memcpy(members[0].calibration_hash, stored.scientific_hash, 32); memcpy(members[1].calibration_hash, stored.scientific_hash, 32); Lardon3DSparseCalibrationMember mismatched_members[2]; memcpy(mismatched_members, members, sizeof(mismatched_members)); mismatched_members[0].calibration_hash[0] ^= 1U; Lardon3DSparseCalibrationScope scope; CHECK(lardon3d_sparse_calibration_scope_create( db, mismatched_members, 2, &scope) != LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_sparse_calibration_scope_create(db, members, 2, &scope) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibrationMember page_members[2] = {members[0], members[1]}; page_members[1].calibration_id = alternate_stored.calibration_id; memcpy(page_members[1].calibration_hash, alternate_stored.scientific_hash, 32); Lardon3DSparseCalibrationScope page_scope; CHECK(lardon3d_sparse_calibration_scope_create( db, page_members, 2, &page_scope) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibrationMember reordered[2] = {members[1], members[0]}; Lardon3DSparseCalibrationScope same_scope; CHECK(lardon3d_sparse_calibration_scope_create( db, reordered, 2, &same_scope) == LARDON3D_PROJECT_DB_OK); CHECK(scope.scope_id == same_scope.scope_id); Lardon3DSparseCalibrationMember page_items[2]; size_t page_count = 0; uint64_t cursor = 0; CHECK(lardon3d_sparse_calibration_scope_list_members( db, scope.scope_id, 0, page_items, 1, &page_count, &cursor) == LARDON3D_PROJECT_DB_OK); CHECK(page_count == 1 && cursor != 0); CHECK(lardon3d_sparse_calibration_scope_list_members( db, scope.scope_id, cursor, page_items, 1, &page_count, &cursor) == LARDON3D_PROJECT_DB_OK); CHECK(page_count == 1); unsigned char parameter_fingerprint[32] = {3}; unsigned char feature_hash_a[32] = {4}; unsigned char feature_hash_b[32] = {5}; unsigned char source_hash_a[32] = {1}; unsigned char source_hash_b[32] = {2}; Lardon3DProjectDbFeatureSet feature_a; Lardon3DProjectDbFeatureSet feature_b; char feature_path_a[128]; char feature_path_b[128]; (void)snprintf(feature_path_a, sizeof(feature_path_a), "assets/features/04/04%062x", 0U); (void)snprintf(feature_path_b, sizeof(feature_path_b), "assets/features/05/05%062x", 0U); Lardon3DProjectDbResult feature_result = lardon3d_project_db_register_feature_set( db, image_a, "orb", 1, parameter_fingerprint, source_hash_a, 128, 1, 32, feature_hash_a, feature_path_a, 128, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 1, &feature_a); if (feature_result != LARDON3D_PROJECT_DB_OK) { char feature_error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; (void)lardon3d_project_db_last_error(db, feature_error); fprintf(stderr, "feature registration failed: %d %s\n", feature_result, feature_error); } CHECK(feature_result == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_project_db_register_feature_set( db, image_b, "orb", 1, parameter_fingerprint, source_hash_b, 10, 1, 32, feature_hash_b, feature_path_b, 128, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 1, &feature_b) == LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbTrackObservation track_observations[4] = { {.feature_set_id = feature_a.feature_set_id, .feature_index = 1, .position_in_track = 0}, {.feature_set_id = feature_b.feature_set_id, .feature_index = 1, .position_in_track = 1}, {.feature_set_id = feature_a.feature_set_id, .feature_index = 2, .position_in_track = 0}, {.feature_set_id = feature_b.feature_set_id, .feature_index = 2, .position_in_track = 1}, }; Lardon3DProjectDbTrack tracks[2] = { {.observation_count = 2, .observations = &track_observations[0]}, {.observation_count = 2, .observations = &track_observations[2]}, }; Lardon3DProjectDbTrackSet track_configuration = {0}; (void)snprintf(track_configuration.builder_kind, sizeof(track_configuration.builder_kind), "test_builder"); track_configuration.builder_version = 1; track_configuration.parameter_fingerprint[0] = 6; track_configuration.verifier_kind = 1; track_configuration.verifier_version = 1; track_configuration.verifier_fingerprint[0] = 7; track_configuration.input_scope_hash[0] = 8; track_configuration.gvr_count = 1; track_configuration.track_count = 2; Lardon3DProjectDbTrackSet published_tracks; CHECK(lardon3d_project_db_create_track_set(db, &track_configuration, tracks, 2, &published_tracks) == LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbTrack loaded_tracks[2]; memset(loaded_tracks, 0, sizeof(loaded_tracks)); size_t loaded_count = 0; CHECK(lardon3d_project_db_list_tracks(db, published_tracks.track_set_id, 0, loaded_tracks, 2, &loaded_count) == LARDON3D_PROJECT_DB_OK); CHECK(loaded_count == 2); Lardon3DSparseComponent component = {.component_key = image_a, .registered_image_count = 2, .landmark_count = 2}; Lardon3DSparseRegisteredImage poses[2] = { {.image_id = image_a, .component_key = image_a, .rotation_cw = {1, 0, 0, 0, 1, 0, 0, 0, 1}, .translation_cw = {0, 0, 0}}, {.image_id = image_b, .component_key = image_a, .rotation_cw = {1, 0, 0, 0, 1, 0, 0, 0, 1}, .translation_cw = {1, 0, 0}}, }; Lardon3DSparseLandmark landmarks[2] = { {.track_id = loaded_tracks[0].track_id, .component_key = image_a, .x = 0, .y = 0, .z = 4, .reprojection_rmse_px = 0.1, .reprojection_median_px = 0.1, .observation_count = 2}, {.track_id = loaded_tracks[1].track_id, .component_key = image_a, .x = 1, .y = 0, .z = 4, .reprojection_rmse_px = 0.1, .reprojection_median_px = 0.1, .observation_count = 2}, }; Lardon3DSparseLandmarkObservation observations[4] = { {.track_id = loaded_tracks[0].track_id, .feature_set_id = feature_a.feature_set_id, .feature_index = 1, .position_in_track = 0}, {.track_id = loaded_tracks[0].track_id, .feature_set_id = feature_b.feature_set_id, .feature_index = 1, .position_in_track = 1}, {.track_id = loaded_tracks[1].track_id, .feature_set_id = feature_a.feature_set_id, .feature_index = 2, .position_in_track = 0}, {.track_id = loaded_tracks[1].track_id, .feature_set_id = feature_b.feature_set_id, .feature_index = 2, .position_in_track = 1}, }; Lardon3DSparsePublication publication = { .track_set_id = published_tracks.track_set_id, .calibration_scope_id = scope.scope_id, .sfm_kind = LARDON3D_SPARSE_SFM_KIND_INCREMENTAL, .sfm_version = LARDON3D_SPARSE_SFM_VERSION, .components = &component, .component_count = 1, .registered_images = poses, .registered_image_count = 2, .landmarks = landmarks, .landmark_count = 2, .observations = observations, .observation_count = 4, .reprojection_rmse_px = 0.1, .reprojection_median_px = 0.1, }; publication.parameter_fingerprint[0] = 9; Lardon3DSparseReconstruction reconstruction; CHECK(lardon3d_sparse_reconstruction_publish( db, &publication, &reconstruction) == LARDON3D_PROJECT_DB_OK); CHECK(!reconstruction.has_derivation_identity && reconstruction.parameter_fingerprint[0] == 9); Lardon3DIncrementalReconstructionMetadata h_metadata = { .base_reconstruction_id = reconstruction.reconstruction_id, .extension_track_set_id = published_tracks.track_set_id, .calibration_scope_id = scope.scope_id, .incremental_kind = 1, .incremental_version = 1, }; h_metadata.parameter_fingerprint[0] = 44; CHECK(test_h_identity(&h_metadata)); memcpy(publication.parameter_fingerprint, h_metadata.parameter_fingerprint, 32); Lardon3DSparseReconstruction h_reconstruction; CHECK(lardon3d_incremental_reconstruction_publish( db, &publication, &h_metadata, &h_reconstruction) == LARDON3D_PROJECT_DB_OK); Lardon3DIncrementalReconstructionMetadata loaded_h_metadata; Lardon3DSparseReconstruction loaded_h_reconstruction; CHECK(lardon3d_incremental_reconstruction_find_exact( db, h_metadata.scientific_identity, &loaded_h_metadata, &loaded_h_reconstruction) == LARDON3D_PROJECT_DB_OK); CHECK(loaded_h_metadata.reconstruction_id == h_reconstruction.reconstruction_id); CHECK(loaded_h_metadata.base_reconstruction_id == reconstruction.reconstruction_id); CHECK(loaded_h_reconstruction.reconstruction_id == h_reconstruction.reconstruction_id); CHECK(loaded_h_reconstruction.has_derivation_identity && memcmp(loaded_h_reconstruction.derivation_identity, h_metadata.scientific_identity, 32) == 0 && memcmp(loaded_h_reconstruction.parameter_fingerprint, h_metadata.parameter_fingerprint, 32) == 0); CHECK(memcmp(loaded_h_metadata.parameter_fingerprint, h_metadata.parameter_fingerprint, 32) == 0); CHECK(memcmp(loaded_h_metadata.scientific_identity, h_metadata.scientific_identity, 32) == 0); Lardon3DSparseReconstruction duplicate_h; Lardon3DSparsePublication mismatched_h_publication = publication; mismatched_h_publication.parameter_fingerprint[0] ^= 0x80; CHECK(lardon3d_incremental_reconstruction_publish( db, &mismatched_h_publication, &h_metadata, &duplicate_h) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); CHECK(raw_scalar_equals(path, "SELECT COUNT(*) FROM sparse_reconstructions", 2)); CHECK(raw_scalar_equals(path, "SELECT COUNT(*) FROM incremental_reconstructions", 1)); CHECK(lardon3d_incremental_reconstruction_publish( db, &publication, &h_metadata, &duplicate_h) == LARDON3D_PROJECT_DB_CONSTRAINT); publication.parameter_fingerprint[0] = 10; Lardon3DSparseReconstruction second_base; CHECK(lardon3d_sparse_reconstruction_publish(db, &publication, &second_base) == LARDON3D_PROJECT_DB_OK); CHECK(!second_base.has_derivation_identity && second_base.parameter_fingerprint[0] == 10); Lardon3DIncrementalReconstructionMetadata second_h_metadata = h_metadata; second_h_metadata.base_reconstruction_id = second_base.reconstruction_id; CHECK(test_h_identity(&second_h_metadata)); memcpy(publication.parameter_fingerprint, second_h_metadata.parameter_fingerprint, 32); Lardon3DSparseReconstruction second_h; CHECK(lardon3d_incremental_reconstruction_publish( db, &publication, &second_h_metadata, &second_h) == LARDON3D_PROJECT_DB_OK); CHECK(second_h.reconstruction_id != h_reconstruction.reconstruction_id && second_h.has_derivation_identity && memcmp(second_h.parameter_fingerprint, h_reconstruction.parameter_fingerprint, 32) == 0 && memcmp(second_h.derivation_identity, h_reconstruction.derivation_identity, 32) != 0); unsigned char valid_identity[32]; memcpy(valid_identity, h_metadata.scientific_identity, 32); h_metadata.scientific_identity[0] ^= 0x80; CHECK(lardon3d_incremental_reconstruction_publish( db, &publication, &h_metadata, &loaded_h_reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); memcpy(h_metadata.scientific_identity, valid_identity, 32); h_metadata.parameter_fingerprint[0] ^= 0x40; CHECK(lardon3d_incremental_reconstruction_publish( db, &publication, &h_metadata, &loaded_h_reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); h_metadata.parameter_fingerprint[0] ^= 0x40; publication.parameter_fingerprint[0] = h_metadata.parameter_fingerprint[0]; h_metadata.base_reconstruction_id = UINT64_C(999999); CHECK(test_h_identity(&h_metadata)); CHECK(lardon3d_incremental_reconstruction_publish( db, &publication, &h_metadata, &loaded_h_reconstruction) == LARDON3D_PROJECT_DB_CONSTRAINT); CHECK(raw_scalar_equals(path, "SELECT COUNT(*) FROM sparse_reconstructions", 4)); publication.parameter_fingerprint[0] = 9; Lardon3DSparseLandmarkPage landmark_page = {0}; Lardon3DSparseLandmark landmark_items[1]; landmark_page.items = landmark_items; Lardon3DProjectDbResult page_result = lardon3d_sparse_landmark_list( db, reconstruction.reconstruction_id, 0, 1, &landmark_page); if (page_result != LARDON3D_PROJECT_DB_OK) { char page_error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; (void)lardon3d_project_db_last_error(db, page_error); fprintf(stderr, "landmark page: %d %s\n", page_result, page_error); } CHECK(page_result == LARDON3D_PROJECT_DB_OK); CHECK(landmark_page.count == 1); CHECK(lardon3d_sparse_landmark_list( db, reconstruction.reconstruction_id, landmark_page.next_track_id, 1, &landmark_page) == LARDON3D_PROJECT_DB_OK); CHECK(landmark_page.count == 1); CHECK(lardon3d_sparse_landmark_list( db, reconstruction.reconstruction_id, landmark_page.next_track_id, 1, &landmark_page) == LARDON3D_PROJECT_DB_OK); CHECK(landmark_page.count == 0); CHECK(lardon3d_sparse_landmark_list(db, reconstruction.reconstruction_id, 0, LARDON3D_SPARSE_SFM_PAGE_MAX + 1, &landmark_page) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); Lardon3DSparseRegisteredImagePage camera_page = {0}; Lardon3DSparseRegisteredImage camera_items[1]; camera_page.items = camera_items; Lardon3DProjectDbResult camera_result = lardon3d_sparse_registered_image_list( db, reconstruction.reconstruction_id, 0, 1, &camera_page); if (camera_result != LARDON3D_PROJECT_DB_OK) { char camera_error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; (void)lardon3d_project_db_last_error(db, camera_error); fprintf(stderr, "camera page: %d %s\n", camera_result, camera_error); } CHECK(camera_result == LARDON3D_PROJECT_DB_OK); CHECK(camera_page.count == 1 && camera_page.next_image_id == image_a); CHECK(lardon3d_sparse_registered_image_list( db, reconstruction.reconstruction_id, camera_page.next_image_id, 1, &camera_page) == LARDON3D_PROJECT_DB_OK); CHECK(camera_page.count == 1 && camera_page.next_image_id == image_b); Lardon3DSparseObservationPage observation_page = {0}; Lardon3DSparseLandmarkObservation observation_items[1]; observation_page.items = observation_items; CHECK(lardon3d_sparse_observation_list(db, reconstruction.reconstruction_id, 0, 0, 1, &observation_page) == LARDON3D_PROJECT_DB_OK); CHECK(observation_page.count == 1); CHECK(lardon3d_sparse_observation_list( db, reconstruction.reconstruction_id, observation_page.next_landmark_id, observation_page.next_position_in_track, 1, &observation_page) == LARDON3D_PROJECT_DB_OK); CHECK(observation_page.count == 1); Lardon3DSparseComponentPage component_page = {0}; Lardon3DSparseComponent component_items[1]; component_page.items = component_items; CHECK(lardon3d_sparse_component_list(db, reconstruction.reconstruction_id, 0, 1, &component_page) == LARDON3D_PROJECT_DB_OK); CHECK(component_page.count == 1 && component_page.next_component_key == image_a); Lardon3DSparseCalibration invalid = calibration; invalid.width = 0; CHECK(lardon3d_sparse_calibration_create(db, &invalid, &duplicate) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid = calibration; invalid.height = 0; CHECK(lardon3d_sparse_calibration_create(db, &invalid, &duplicate) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid = calibration; invalid.fx = 0.0; CHECK(lardon3d_sparse_calibration_create(db, &invalid, &duplicate) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid = calibration; invalid.fy = -1.0; CHECK(lardon3d_sparse_calibration_create(db, &invalid, &duplicate) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid = calibration; invalid.model_kind = 99; CHECK(lardon3d_sparse_calibration_create(db, &invalid, &duplicate) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid = calibration; invalid.fx = NAN; CHECK(lardon3d_sparse_calibration_create(db, &invalid, &duplicate) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid = calibration; invalid.fy = INFINITY; CHECK(lardon3d_sparse_calibration_create(db, &invalid, &duplicate) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); const char *lengths[] = {"0", "1", "31", "33", "64"}; for (size_t index = 0; index < sizeof(lengths) / sizeof(lengths[0]); ++index) { char sql[512]; (void)snprintf( sql, sizeof(sql), "INSERT INTO sparse_calibrations " "(scientific_hash,model_kind,model_version,width,height,fx,fy,cx,cy," "k1,k2,p1,p2,provenance_kind,provenance_fingerprint) VALUES " "(randomblob(32),1,1,4000,3000,2000,2000,2000,1500,0,0,0,0,1," "zeroblob(%s))", lengths[index]); CHECK(raw_constraint(path, sql)); (void)snprintf( sql, sizeof(sql), "INSERT INTO sparse_calibration_scopes " "(scientific_hash,member_count) VALUES(randomblob(%s),1)", lengths[index]); CHECK(raw_constraint(path, sql)); } lardon3d_project_db_close(db); db = NULL; for (size_t index = 0; index < sizeof(lengths) / sizeof(lengths[0]); ++index) { char sql[512]; (void)snprintf( sql, sizeof(sql), "INSERT INTO sparse_reconstructions " "(track_set_id,calibration_scope_id,sfm_kind,sfm_version," "parameter_fingerprint,component_count,registered_image_count," "landmark_count,reprojection_rmse_px,reprojection_median_px,created_at)" " VALUES(%llu,%llu,1,1,zeroblob(%s),1,2,1,0.1,0.1,0)", (unsigned long long)published_tracks.track_set_id, (unsigned long long)scope.scope_id, lengths[index]); CHECK(raw_constraint(path, sql)); } CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec(path, "UPDATE sparse_calibration_scopes SET member_count=1") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibrationScope corrupt_scope; CHECK(lardon3d_sparse_calibration_scope_load(db, scope.scope_id, &corrupt_scope) == LARDON3D_PROJECT_DB_CORRUPT); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec(path, "UPDATE sparse_calibration_scopes SET member_count=2") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseReconstruction reopened; CHECK( lardon3d_sparse_reconstruction_load(db, reconstruction.reconstruction_id, &reopened) == LARDON3D_PROJECT_DB_OK); CHECK(reopened.landmark_count == reconstruction.landmark_count); for (size_t index = 0; index < loaded_count; ++index) lardon3d_project_db_free_track(&loaded_tracks[index]); lardon3d_project_db_close(db); db = NULL; char corrupt_calibration_sql[128]; (void)snprintf(corrupt_calibration_sql, sizeof(corrupt_calibration_sql), "PRAGMA ignore_check_constraints=ON;UPDATE " "sparse_calibrations SET width=0 WHERE calibration_id=%llu", (unsigned long long)alternate_stored.calibration_id); CHECK(raw_exec(path, corrupt_calibration_sql) == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibrationMember scope_page_items[2]; size_t scope_page_count = 0; uint64_t scope_page_cursor = 0; CHECK(lardon3d_sparse_calibration_scope_list_members( db, page_scope.scope_id, 0, scope_page_items, 2, &scope_page_count, &scope_page_cursor) == LARDON3D_PROJECT_DB_CORRUPT); CHECK(scope_page_count == 0 && scope_page_cursor == 0); lardon3d_project_db_close(db); unlink(path); return true; } static bool register_fixture_feature_set(Lardon3DProjectDb *db, uint64_t image_id, unsigned char seed, Lardon3DProjectDbFeatureSet *output) { unsigned char parameter_hash[32] = {11}; unsigned char source_hash[32] = {0}; unsigned char asset_hash[32] = {0}; char path[128]; char hex[65]; static const char digits[] = "0123456789abcdef"; source_hash[0] = seed; asset_hash[0] = seed; for (size_t index = 0; index < 32; ++index) { hex[index * 2] = digits[asset_hash[index] >> 4]; hex[index * 2 + 1] = digits[asset_hash[index] & 15U]; } hex[64] = '\0'; (void)snprintf(path, sizeof(path), "assets/features/%c%c/%s", hex[0], hex[1], hex); return lardon3d_project_db_register_feature_set( db, image_id, "synthetic", 1, parameter_hash, source_hash, 3, 1, 32, asset_hash, path, 64, LARDON3D_DB_FEATURE_ASSET_DURABLE, 0, 1, output) == LARDON3D_PROJECT_DB_OK; } static bool page_fixture(Lardon3DProjectDb *db, uint64_t reconstruction_id, const uint64_t image_ids[4], const uint64_t track_ids[4], const uint64_t feature_set_ids[4]) { Lardon3DSparseComponentPage components = {0}; Lardon3DSparseComponent component_item; components.items = &component_item; uint64_t after_component = 0; size_t component_index = 0; for (;;) { CHECK(lardon3d_sparse_component_list(db, reconstruction_id, after_component, 1, &components) == LARDON3D_PROJECT_DB_OK); if (components.count == 0) break; CHECK(component_index < 2); CHECK(component_item.component_key == image_ids[component_index * 2]); CHECK(component_item.registered_image_count == 2); CHECK(component_item.landmark_count == 2); after_component = components.next_component_key; ++component_index; } CHECK(component_index == 2); Lardon3DSparseRegisteredImagePage cameras = {0}; Lardon3DSparseRegisteredImage camera_item; cameras.items = &camera_item; uint64_t after_image = 0; size_t image_index = 0; for (;;) { CHECK(lardon3d_sparse_registered_image_list( db, reconstruction_id, after_image, 1, &cameras) == LARDON3D_PROJECT_DB_OK); if (cameras.count == 0) break; CHECK(image_index < 4); CHECK(camera_item.image_id == image_ids[image_index]); CHECK(camera_item.component_key == image_ids[image_index < 2 ? 0 : 2]); after_image = cameras.next_image_id; ++image_index; } CHECK(image_index == 4); Lardon3DSparseLandmarkPage landmarks = {0}; Lardon3DSparseLandmark landmark_item; landmarks.items = &landmark_item; uint64_t after_track = 0; size_t landmark_index = 0; for (;;) { CHECK(lardon3d_sparse_landmark_list(db, reconstruction_id, after_track, 1, &landmarks) == LARDON3D_PROJECT_DB_OK); if (landmarks.count == 0) break; CHECK(landmark_index < 4); CHECK(landmark_item.track_id == track_ids[landmark_index]); CHECK(landmark_item.component_key == image_ids[landmark_index < 2 ? 0 : 2]); CHECK(landmark_item.observation_count == 2); after_track = landmarks.next_track_id; ++landmark_index; } CHECK(landmark_index == 4); Lardon3DSparseObservationPage observations = {0}; Lardon3DSparseLandmarkObservation observation_item; observations.items = &observation_item; uint64_t after_landmark = 0; uint32_t after_position = 0; size_t observation_index = 0; for (;;) { CHECK(lardon3d_sparse_observation_list( db, reconstruction_id, after_landmark, after_position, 1, &observations) == LARDON3D_PROJECT_DB_OK); if (observations.count == 0) break; CHECK(observation_index < 8); CHECK(observation_item.track_id == track_ids[observation_index / 2]); CHECK(observation_item.feature_set_id == feature_set_ids[(observation_index / 2) < 2 ? observation_index % 2 : 2 + observation_index % 2]); CHECK(observation_item.position_in_track == observation_index % 2); after_landmark = observations.next_landmark_id; after_position = observations.next_position_in_track; ++observation_index; } CHECK(observation_index == 8); return true; } static bool run_multi_component_test(void) { char path[] = "/tmp/lardon3d-sparse-components-XXXXXX"; int descriptor = mkstemp(path); CHECK(descriptor >= 0); close(descriptor); unlink(path); Lardon3DProjectDb *db = NULL; char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbScanSet scanset; CHECK(lardon3d_project_db_create_scanset(db, "two components", &scanset) == LARDON3D_PROJECT_DB_OK); uint64_t image_ids[4] = {0}; Lardon3DProjectDbFeatureSet features[4]; for (size_t index = 0; index < 4; ++index) { CHECK(register_image(db, scanset.scanset_id, (unsigned char)(20 + index), &image_ids[index])); CHECK(register_fixture_feature_set(db, image_ids[index], (unsigned char)(20 + index), &features[index])); } Lardon3DSparseCalibration calibration = {0}; calibration.model_kind = LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE; calibration.model_version = LARDON3D_SPARSE_SFM_CALIBRATION_VERSION; calibration.width = 4000; calibration.height = 3000; calibration.fx = 2000.0; calibration.fy = 2000.0; calibration.cx = 2000.0; calibration.cy = 1500.0; calibration.provenance_kind = LARDON3D_SPARSE_SFM_PROVENANCE_USER_EXPLICIT; Lardon3DSparseCalibration stored; CHECK(lardon3d_sparse_calibration_create(db, &calibration, &stored) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibrationMember members[4]; for (size_t index = 0; index < 4; ++index) { members[index].image_id = image_ids[index]; members[index].calibration_id = stored.calibration_id; memcpy(members[index].calibration_hash, stored.scientific_hash, 32); } Lardon3DSparseCalibrationScope scope; CHECK(lardon3d_sparse_calibration_scope_create(db, members, 4, &scope) == LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbTrackObservation track_observations[8]; Lardon3DProjectDbTrack tracks[4]; for (size_t track = 0; track < 4; ++track) { size_t first = track * 2; size_t first_image = track < 2 ? 0 : 2; track_observations[first] = (Lardon3DProjectDbTrackObservation){ .feature_set_id = features[first_image].feature_set_id, .feature_index = (uint32_t)(track % 2 + 1), .position_in_track = 0}; track_observations[first + 1] = (Lardon3DProjectDbTrackObservation){ .feature_set_id = features[first_image + 1].feature_set_id, .feature_index = (uint32_t)(track % 2 + 1), .position_in_track = 1}; tracks[track] = (Lardon3DProjectDbTrack){ .observation_count = 2, .observations = &track_observations[first]}; } Lardon3DProjectDbTrackSet configuration = {0}; (void)snprintf(configuration.builder_kind, sizeof(configuration.builder_kind), "synthetic_components"); configuration.builder_version = 1; configuration.parameter_fingerprint[0] = 31; configuration.verifier_kind = 1; configuration.verifier_version = 1; configuration.verifier_fingerprint[0] = 32; configuration.input_scope_hash[0] = 33; configuration.gvr_count = 1; configuration.track_count = 4; Lardon3DProjectDbTrackSet track_set; Lardon3DProjectDbResult track_result = lardon3d_project_db_create_track_set( db, &configuration, tracks, 4, &track_set); if (track_result != LARDON3D_PROJECT_DB_OK) { char track_error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]; (void)lardon3d_project_db_last_error(db, track_error); fprintf(stderr, "multi-component track set: %d %s\n", track_result, track_error); } CHECK(track_result == LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbTrack loaded_tracks[4] = {0}; size_t loaded_count = 0; CHECK(lardon3d_project_db_list_tracks(db, track_set.track_set_id, 0, loaded_tracks, 4, &loaded_count) == LARDON3D_PROJECT_DB_OK); CHECK(loaded_count == 4); uint64_t track_ids[4]; for (size_t index = 0; index < 4; ++index) track_ids[index] = loaded_tracks[index].track_id; for (size_t index = 0; index < 4; ++index) lardon3d_project_db_free_track(&loaded_tracks[index]); Lardon3DSparseComponent components[2] = { {.component_key = image_ids[0], .registered_image_count = 2, .landmark_count = 2}, {.component_key = image_ids[2], .registered_image_count = 2, .landmark_count = 2}}; Lardon3DSparseRegisteredImage poses[4]; for (size_t index = 0; index < 4; ++index) { poses[index] = (Lardon3DSparseRegisteredImage){ .image_id = image_ids[index], .component_key = image_ids[index < 2 ? 0 : 2], .rotation_cw = {1, 0, 0, 0, 1, 0, 0, 0, 1}, .translation_cw = {(double)index, 0, 0}}; } Lardon3DSparseLandmark landmarks[4]; for (size_t index = 0; index < 4; ++index) landmarks[index] = (Lardon3DSparseLandmark){ .track_id = track_ids[index], .component_key = image_ids[index < 2 ? 0 : 2], .x = (double)index, .y = 0, .z = 4, .reprojection_rmse_px = 0.1, .reprojection_median_px = 0.1, .observation_count = 2}; Lardon3DSparseLandmarkObservation observations[8]; for (size_t index = 0; index < 8; ++index) observations[index] = (Lardon3DSparseLandmarkObservation){ .track_id = track_ids[index / 2], .feature_set_id = features[index / 2 < 2 ? index / 2 : 2 + index / 2 - 2] .feature_set_id, .feature_index = (uint32_t)(index / 2 % 2 + 1), .position_in_track = (uint32_t)(index % 2)}; for (size_t index = 0; index < 8; ++index) observations[index].feature_set_id = features[(index / 2 < 2 ? 0 : 2) + index % 2].feature_set_id; Lardon3DSparsePublication publication = { .track_set_id = track_set.track_set_id, .calibration_scope_id = scope.scope_id, .sfm_kind = LARDON3D_SPARSE_SFM_KIND_INCREMENTAL, .sfm_version = LARDON3D_SPARSE_SFM_VERSION, .components = components, .component_count = 2, .registered_images = poses, .registered_image_count = 4, .landmarks = landmarks, .landmark_count = 4, .observations = observations, .observation_count = 8, .reprojection_rmse_px = 0.1, .reprojection_median_px = 0.1}; publication.parameter_fingerprint[0] = 34; Lardon3DSparseReconstruction reconstruction; CHECK(lardon3d_sparse_reconstruction_publish(db, &publication, &reconstruction) == LARDON3D_PROJECT_DB_OK); publication.parameter_fingerprint[0] = 47; Lardon3DSparseReconstruction deleted_reconstruction; CHECK(lardon3d_sparse_reconstruction_publish( db, &publication, &deleted_reconstruction) == LARDON3D_PROJECT_DB_OK); publication.parameter_fingerprint[0] = 34; uint64_t deleted_id = deleted_reconstruction.reconstruction_id; lardon3d_project_db_close(db); db = NULL; char lifecycle_sql[256]; (void)snprintf(lifecycle_sql, sizeof(lifecycle_sql), "DELETE FROM sparse_reconstructions WHERE reconstruction_id=%llu", (unsigned long long)deleted_id); CHECK(raw_exec(path, lifecycle_sql) == SQLITE_OK); (void)snprintf(lifecycle_sql, sizeof(lifecycle_sql), "SELECT COUNT(*) FROM sparse_reconstructions " "WHERE reconstruction_id=%llu", (unsigned long long)deleted_id); CHECK(raw_scalar_equals(path, lifecycle_sql, 0)); (void)snprintf(lifecycle_sql, sizeof(lifecycle_sql), "SELECT COUNT(*) FROM sparse_reconstruction_components " "WHERE reconstruction_id=%llu", (unsigned long long)deleted_id); CHECK(raw_scalar_equals(path, lifecycle_sql, 0)); (void)snprintf(lifecycle_sql, sizeof(lifecycle_sql), "SELECT COUNT(*) FROM sparse_registered_images " "WHERE reconstruction_id=%llu", (unsigned long long)deleted_id); CHECK(raw_scalar_equals(path, lifecycle_sql, 0)); (void)snprintf(lifecycle_sql, sizeof(lifecycle_sql), "SELECT COUNT(*) FROM sparse_landmarks " "WHERE reconstruction_id=%llu", (unsigned long long)deleted_id); CHECK(raw_scalar_equals(path, lifecycle_sql, 0)); (void)snprintf(lifecycle_sql, sizeof(lifecycle_sql), "SELECT COUNT(*) FROM sparse_landmark_observations AS o " "JOIN sparse_landmarks AS l ON l.landmark_id=o.landmark_id " "WHERE l.reconstruction_id=%llu", (unsigned long long)deleted_id); CHECK(raw_scalar_equals(path, lifecycle_sql, 0)); CHECK(raw_scalar_equals( path, "SELECT COUNT(*) FROM sparse_reconstruction_components AS c " "WHERE NOT EXISTS (SELECT 1 FROM sparse_reconstructions AS r " "WHERE r.reconstruction_id=c.reconstruction_id)", 0)); CHECK(raw_scalar_equals( path, "SELECT COUNT(*) FROM sparse_registered_images AS p WHERE " "NOT EXISTS (SELECT 1 FROM sparse_reconstructions AS r WHERE " "r.reconstruction_id=p.reconstruction_id) OR NOT EXISTS " "(SELECT 1 FROM sparse_reconstruction_components AS c WHERE " "c.reconstruction_id=p.reconstruction_id AND c.component_id=" "p.component_id)", 0)); CHECK(raw_scalar_equals( path, "SELECT COUNT(*) FROM sparse_landmarks AS l WHERE NOT EXISTS " "(SELECT 1 FROM sparse_reconstructions AS r WHERE " "r.reconstruction_id=l.reconstruction_id) OR NOT EXISTS " "(SELECT 1 FROM sparse_reconstruction_components AS c WHERE " "c.reconstruction_id=l.reconstruction_id AND c.component_id=" "l.component_id)", 0)); CHECK(raw_scalar_equals( path, "SELECT COUNT(*) FROM sparse_landmark_observations AS o WHERE " "NOT EXISTS (SELECT 1 FROM sparse_landmarks AS l WHERE " "l.landmark_id=o.landmark_id)", 0)); CHECK(raw_constraint(path, "DELETE FROM tracks WHERE track_id=1")); CHECK(raw_constraint(path, "DELETE FROM track_sets WHERE track_set_id=1")); CHECK(raw_constraint(path, "DELETE FROM images WHERE image_id=1")); CHECK(raw_constraint(path, "DELETE FROM feature_sets WHERE feature_set_id=1")); CHECK(raw_constraint(path, "DELETE FROM sparse_calibrations WHERE calibration_id=1")); CHECK(raw_constraint(path, "DELETE FROM sparse_calibration_scopes WHERE scope_id=1")); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibration surviving_calibration; CHECK(lardon3d_sparse_calibration_load(db, stored.calibration_id, &surviving_calibration) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseCalibrationScope surviving_scope; CHECK(lardon3d_sparse_calibration_scope_load(db, scope.scope_id, &surviving_scope) == LARDON3D_PROJECT_DB_OK); Lardon3DProjectDbTrackSet surviving_track_set; CHECK(lardon3d_project_db_load_track_set(db, track_set.track_set_id, &surviving_track_set) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_sparse_reconstruction_load(db, reconstruction.reconstruction_id, &reconstruction) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseRegisteredImage wrong_component_poses[4]; memcpy(wrong_component_poses, poses, sizeof(wrong_component_poses)); wrong_component_poses[0].component_key = image_ids[2]; Lardon3DSparsePublication invalid_publication = publication; invalid_publication.registered_images = wrong_component_poses; invalid_publication.parameter_fingerprint[0] = 39; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); Lardon3DSparseLandmark wrong_component_landmarks[4]; memcpy(wrong_component_landmarks, landmarks, sizeof(wrong_component_landmarks)); wrong_component_landmarks[0].component_key = image_ids[2]; invalid_publication = publication; invalid_publication.landmarks = wrong_component_landmarks; invalid_publication.parameter_fingerprint[0] = 40; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); memcpy(wrong_component_poses, poses, sizeof(wrong_component_poses)); wrong_component_poses[1].image_id = image_ids[0]; invalid_publication = publication; invalid_publication.registered_images = wrong_component_poses; invalid_publication.parameter_fingerprint[0] = 41; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid_publication = publication; invalid_publication.track_set_id = track_set.track_set_id + 1000; invalid_publication.parameter_fingerprint[0] = 42; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) == LARDON3D_PROJECT_DB_NOT_FOUND); Lardon3DSparseLandmark duplicate_landmarks[4]; memcpy(duplicate_landmarks, landmarks, sizeof(duplicate_landmarks)); duplicate_landmarks[1].track_id = duplicate_landmarks[0].track_id; invalid_publication = publication; invalid_publication.landmarks = duplicate_landmarks; invalid_publication.parameter_fingerprint[0] = 43; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); Lardon3DSparseLandmarkObservation invalid_observations[8]; memcpy(invalid_observations, observations, sizeof(invalid_observations)); invalid_observations[0].track_id = track_ids[1]; invalid_publication = publication; invalid_publication.observations = invalid_observations; invalid_publication.parameter_fingerprint[0] = 44; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) != LARDON3D_PROJECT_DB_OK); memcpy(invalid_observations, observations, sizeof(invalid_observations)); invalid_observations[0].feature_index = 99; invalid_publication.parameter_fingerprint[0] = 45; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) != LARDON3D_PROJECT_DB_OK); memcpy(invalid_observations, observations, sizeof(invalid_observations)); invalid_observations[1] = invalid_observations[0]; invalid_publication.parameter_fingerprint[0] = 46; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_publication, &reconstruction) != LARDON3D_PROJECT_DB_OK); Lardon3DSparseRegisteredImage invalid_pose = poses[0]; invalid_pose.rotation_cw[0] = 2.0; Lardon3DSparsePublication invalid_pose_publication = publication; invalid_pose_publication.registered_images = &invalid_pose; invalid_pose_publication.registered_image_count = 1; invalid_pose_publication.parameter_fingerprint[0] = 37; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_pose_publication, &reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); invalid_pose = poses[0]; invalid_pose.translation_cw[0] = NAN; invalid_pose_publication.registered_images = &invalid_pose; invalid_pose_publication.parameter_fingerprint[0] = 38; CHECK(lardon3d_sparse_reconstruction_publish( db, &invalid_pose_publication, &reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); CHECK(reconstruction.component_count == 2); CHECK(reconstruction.registered_image_count == 4); CHECK(reconstruction.landmark_count == 4); CHECK(page_fixture(db, reconstruction.reconstruction_id, image_ids, track_ids, (uint64_t[4]){features[0].feature_set_id, features[1].feature_set_id, features[2].feature_set_id, features[3].feature_set_id})); Lardon3DSparseComponent unsorted_components[2] = {components[1], components[0]}; Lardon3DSparsePublication unsorted_publication = publication; unsorted_publication.components = unsorted_components; unsorted_publication.parameter_fingerprint[0] = 36; CHECK(lardon3d_sparse_reconstruction_publish(db, &unsorted_publication, &reconstruction) == LARDON3D_PROJECT_DB_INVALID_ARGUMENT); Lardon3DSparseLandmarkObservation invalid = observations[0]; invalid.feature_set_id = features[2].feature_set_id; invalid_publication = publication; invalid_publication.observations = &invalid; invalid_publication.observation_count = 1; invalid_publication.parameter_fingerprint[0] = 35; CHECK(lardon3d_sparse_reconstruction_publish(db, &invalid_publication, &reconstruction) != LARDON3D_PROJECT_DB_OK); Lardon3DSparseReconstructionPage reconstruction_page = {0}; Lardon3DSparseReconstruction reconstruction_items[2]; reconstruction_page.items = reconstruction_items; CHECK(lardon3d_sparse_reconstruction_list(db, 0, 2, &reconstruction_page) == LARDON3D_PROJECT_DB_OK); CHECK(reconstruction_page.count == 1); uint64_t reconstruction_id = reconstruction.reconstruction_id; lardon3d_project_db_close(db); db = NULL; CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseReconstruction reopened; CHECK(lardon3d_sparse_reconstruction_load(db, reconstruction_id, &reopened) == LARDON3D_PROJECT_DB_OK); CHECK(reopened.component_count == 2 && reopened.registered_image_count == 4 && reopened.landmark_count == 4); CHECK(page_fixture(db, reconstruction_id, image_ids, track_ids, (uint64_t[4]){features[0].feature_set_id, features[1].feature_set_id, features[2].feature_set_id, features[3].feature_set_id})); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec( path, "PRAGMA ignore_check_constraints=ON;" "UPDATE sparse_reconstructions SET sfm_kind=99") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_sparse_reconstruction_load(db, reconstruction_id, &reopened) == LARDON3D_PROJECT_DB_CORRUPT); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec( path, "PRAGMA ignore_check_constraints=ON;" "UPDATE sparse_reconstructions SET sfm_kind=1") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); CHECK(lardon3d_sparse_reconstruction_load(db, reconstruction_id, &reopened) == LARDON3D_PROJECT_DB_OK); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec( path, "UPDATE sparse_registered_images SET component_id=" "(SELECT component_id FROM sparse_reconstruction_components " "WHERE reconstruction_id=(SELECT reconstruction_id FROM " "sparse_registered_images LIMIT 1) ORDER BY component_key DESC " "LIMIT 1) WHERE image_id=(SELECT MIN(image_id) FROM " "sparse_registered_images)") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseRegisteredImagePage wrong_component_camera_page = {0}; Lardon3DSparseRegisteredImage wrong_component_camera; wrong_component_camera_page.items = &wrong_component_camera; CHECK(lardon3d_sparse_registered_image_list(db, reconstruction_id, 0, 1, &wrong_component_camera_page) == LARDON3D_PROJECT_DB_CORRUPT); Lardon3DSparseComponentPage wrong_component_page = {0}; Lardon3DSparseComponent wrong_component; wrong_component_page.items = &wrong_component; CHECK(lardon3d_sparse_component_list(db, reconstruction_id, 0, 1, &wrong_component_page) == LARDON3D_PROJECT_DB_CORRUPT); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec( path, "UPDATE sparse_registered_images SET component_id=" "(SELECT component_id FROM sparse_reconstruction_components " "WHERE reconstruction_id=(SELECT reconstruction_id FROM " "sparse_registered_images LIMIT 1) AND component_key=" "(SELECT MIN(component_key) FROM sparse_reconstruction_components)) " "WHERE image_id=(SELECT MIN(image_id) FROM sparse_registered_images);" "UPDATE sparse_landmarks SET component_id=" "(SELECT component_id FROM sparse_reconstruction_components " "WHERE reconstruction_id=(SELECT reconstruction_id FROM " "sparse_landmarks LIMIT 1) ORDER BY component_key DESC LIMIT 1) " "WHERE landmark_id=(SELECT MIN(landmark_id) FROM sparse_landmarks)") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseLandmarkPage wrong_component_landmark_page = {0}; Lardon3DSparseLandmark wrong_component_landmark; wrong_component_landmark_page.items = &wrong_component_landmark; CHECK(lardon3d_sparse_landmark_list(db, reconstruction_id, 0, 1, &wrong_component_landmark_page) == LARDON3D_PROJECT_DB_CORRUPT); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec( path, "UPDATE sparse_landmarks SET component_id=" "(SELECT component_id FROM sparse_reconstruction_components " "WHERE reconstruction_id=(SELECT reconstruction_id FROM " "sparse_landmarks LIMIT 1) AND component_key=" "(SELECT MIN(f.image_id) FROM track_observations AS t JOIN " "feature_sets AS f ON f.feature_set_id=t.feature_set_id JOIN " "sparse_reconstructions AS r ON r.track_set_id=t.track_set_id " "WHERE r.reconstruction_id=(SELECT reconstruction_id FROM " "sparse_landmarks LIMIT 1) AND t.track_id=(SELECT track_id FROM " "sparse_landmarks ORDER BY landmark_id LIMIT 1))) WHERE " "landmark_id=(SELECT MIN(landmark_id) FROM sparse_landmarks)") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec(path, "UPDATE sparse_registered_images SET rotation_00=2.0") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseRegisteredImagePage corrupt_camera_page = {0}; Lardon3DSparseRegisteredImage corrupt_camera; corrupt_camera_page.items = &corrupt_camera; CHECK(lardon3d_sparse_registered_image_list(db, reconstruction_id, 0, 1, &corrupt_camera_page) == LARDON3D_PROJECT_DB_CORRUPT); lardon3d_project_db_close(db); db = NULL; CHECK(raw_exec( path, "UPDATE sparse_registered_images SET rotation_00=1.0;" "UPDATE sparse_landmark_observations SET feature_set_id=" "(SELECT feature_set_id FROM feature_sets ORDER BY feature_set_id " "LIMIT 1 OFFSET 2) " "WHERE landmark_id=(SELECT landmark_id FROM sparse_landmarks " "ORDER BY landmark_id LIMIT 1) AND position_in_track=0") == SQLITE_OK); CHECK(lardon3d_project_db_open(path, &db, error) == LARDON3D_PROJECT_DB_OK); Lardon3DSparseObservationPage corrupt_observation_page = {0}; Lardon3DSparseLandmarkObservation corrupt_observation; corrupt_observation_page.items = &corrupt_observation; CHECK(lardon3d_sparse_observation_list(db, reconstruction_id, 0, 0, 1, &corrupt_observation_page) == LARDON3D_PROJECT_DB_CORRUPT); lardon3d_project_db_close(db); unlink(path); return true; } int main(void) { return run_test() && run_multi_component_test() ? EXIT_SUCCESS : EXIT_FAILURE; }