646 lines
32 KiB
C++
646 lines
32 KiB
C++
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <map>
|
|
#include <sched.h>
|
|
#include <string>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
#include <lardon3d/app_state.h>
|
|
#include <lardon3d/feature_store.h>
|
|
#include <lardon3d/incremental_reconstruction.h>
|
|
#include <lardon3d/incremental_reconstruction_task.h>
|
|
#include <lardon3d/project_db.h>
|
|
#include <lardon3d/resource_governor.h>
|
|
#include <lardon3d/sparse_sfm_model.h>
|
|
#include <lardon3d/sparse_sfm_task.h>
|
|
#include <lardon3d/task_checkpoint.h>
|
|
#include <lardon3d/task_kind_registry.h>
|
|
#include <lardon3d/task_queue.h>
|
|
}
|
|
#include "../src/sparse_sfm_gate_f_internal.h"
|
|
|
|
extern "C" int lardon3d_sparse_sfm_task_test_observation_lookup(
|
|
const Lardon3DSparseIncrementalObservation *observations, size_t count,
|
|
uint64_t track_id, uint64_t image_id, uint64_t feature_set_id,
|
|
uint32_t feature_index, size_t *resolved_index);
|
|
extern "C" bool lardon3d_sparse_sfm_task_test_project_landmarks(
|
|
const Lardon3DSparseIncrementalLandmark *landmarks, size_t landmark_count,
|
|
const uint64_t *component_keys, size_t component_count,
|
|
Lardon3DSparseLandmark *output, size_t output_capacity, size_t *output_count);
|
|
|
|
namespace {
|
|
#define CHECK(value) \
|
|
do { \
|
|
if (!(value)) { \
|
|
std::fprintf(stderr, "sparse-sfm-task:%d: %s\n", __LINE__, #value); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
constexpr size_t kBaseImageCount = 3;
|
|
constexpr size_t kImageCount = 4;
|
|
constexpr size_t kTrackCount = 12;
|
|
|
|
struct Fixture {
|
|
std::string root;
|
|
std::string database_path;
|
|
Lardon3DAppState state{};
|
|
Lardon3DProjectDbScanSet scanset{};
|
|
Lardon3DProjectDbImage images[kImageCount]{};
|
|
Lardon3DProjectDbFeatureSet feature_sets[kImageCount]{};
|
|
Lardon3DProjectDbTrackSet base_track_set{};
|
|
Lardon3DProjectDbTrackSet track_set{};
|
|
Lardon3DSparseCalibrationScope scope{};
|
|
Lardon3DSparseIncrementalParameters parameters{};
|
|
};
|
|
|
|
Lardon3DResourcePolicy policy() {
|
|
return {0, 0, 0, 0, 1.0, 100.0, 100.0, 100.0, 0, 1};
|
|
}
|
|
|
|
bool wait_state(Lardon3DTaskQueue *queue, uint64_t id, Lardon3DTaskState wanted) {
|
|
timespec start{};
|
|
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) return false;
|
|
const int64_t deadline = start.tv_sec * INT64_C(1000000000) + start.tv_nsec +
|
|
INT64_C(10000000000);
|
|
for (;;) {
|
|
Lardon3DTaskSnapshot snapshot{};
|
|
if (lardon3d_task_queue_get(queue, id, &snapshot) && snapshot.state == wanted) return true;
|
|
timespec now{};
|
|
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0 ||
|
|
now.tv_sec * INT64_C(1000000000) + now.tv_nsec >= deadline)
|
|
return false;
|
|
sched_yield();
|
|
}
|
|
}
|
|
|
|
bool wait_reservations_released(Lardon3DResourceGovernor *governor) {
|
|
timespec start{};
|
|
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) return false;
|
|
const int64_t deadline = start.tv_sec * INT64_C(1000000000) + start.tv_nsec +
|
|
INT64_C(10000000000);
|
|
while (lardon3d_resource_governor_reservation_count(governor) != 0) {
|
|
timespec now{};
|
|
if (clock_gettime(CLOCK_MONOTONIC, &now) != 0 ||
|
|
now.tv_sec * INT64_C(1000000000) + now.tv_nsec >= deadline)
|
|
return false;
|
|
sched_yield();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
Lardon3DSparseGeometryPoint2 project(const Lardon3DSparseGeometryCalibration &calibration,
|
|
size_t camera,
|
|
const Lardon3DSparseGeometryPoint3 &point) {
|
|
double x = point.x + static_cast<double>(camera);
|
|
return {calibration.fx * x / point.z + calibration.cx,
|
|
calibration.fy * point.y / point.z + calibration.cy};
|
|
}
|
|
|
|
std::string asset_path(const unsigned char hash[32], const char *kind) {
|
|
static constexpr char digits[] = "0123456789abcdef";
|
|
std::string hex(64, '0');
|
|
for (size_t index = 0; index < 32; ++index) {
|
|
hex[2 * index] = digits[hash[index] >> 4];
|
|
hex[2 * index + 1] = digits[hash[index] & 15U];
|
|
}
|
|
return std::string("assets/") + kind + "/" + hex.substr(0, 2) + "/" + hex;
|
|
}
|
|
|
|
bool initialize_runtime(Fixture *fixture) {
|
|
lardon3d_app_state_init(&fixture->state);
|
|
fixture->state.hardware_profile.logical_cpu_count = 16;
|
|
fixture->state.hardware_profile.page_size_bytes = 4096;
|
|
fixture->state.hardware_profile.memory_total_bytes = 64ULL * 1024 * 1024 * 1024;
|
|
std::snprintf(fixture->state.hardware_profile.cpu_architecture,
|
|
sizeof(fixture->state.hardware_profile.cpu_architecture), "test");
|
|
auto resource_policy = policy();
|
|
fixture->state.resource_governor =
|
|
lardon3d_resource_governor_create(&fixture->state.hardware_profile, &resource_policy);
|
|
fixture->state.task_queue = fixture->state.resource_governor
|
|
? lardon3d_task_queue_create(
|
|
fixture->state.resource_governor, 8)
|
|
: nullptr;
|
|
return fixture->state.task_queue != nullptr;
|
|
}
|
|
|
|
bool create_fixture(Fixture *fixture) {
|
|
char root[] = "/tmp/lardon3d-sparse-task-XXXXXX";
|
|
CHECK(mkdtemp(root) != nullptr);
|
|
fixture->root = root;
|
|
fixture->database_path = fixture->root + "/project.db";
|
|
CHECK(mkdir((fixture->root + "/.lardon3d").c_str(), 0700) == 0);
|
|
CHECK(mkdir((fixture->root + "/.lardon3d/checkpoints").c_str(), 0700) == 0);
|
|
CHECK(initialize_runtime(fixture));
|
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
|
CHECK(lardon3d_project_db_open(fixture->database_path.c_str(),
|
|
&fixture->state.project_db, error) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
fixture->state.project_loaded = true;
|
|
CHECK(std::snprintf(fixture->state.project_path, sizeof(fixture->state.project_path), "%s",
|
|
fixture->root.c_str()) > 0);
|
|
CHECK(lardon3d_project_db_create_scanset(fixture->state.project_db, "sfm-task",
|
|
&fixture->scanset) == LARDON3D_PROJECT_DB_OK);
|
|
|
|
const Lardon3DSparseGeometryCalibration geometry = {
|
|
4000, 3000, 2000.0, 2000.0, 2000.0, 1500.0, 0.0, 0.0, 0.0, 0.0};
|
|
std::vector<Lardon3DSparseGeometryPoint3> points;
|
|
for (size_t track = 0; track < kTrackCount; ++track)
|
|
points.push_back({-1.0 + static_cast<double>(track % 4) * 0.5,
|
|
-0.8 + static_cast<double>(track / 4) * 0.4,
|
|
5.0 + static_cast<double>(track % 3) * 0.25});
|
|
|
|
unsigned char feature_fingerprint[32]{};
|
|
for (size_t camera = 0; camera < kImageCount; ++camera) {
|
|
unsigned char image_hash[32]{};
|
|
image_hash[0] = static_cast<unsigned char>(camera + 1);
|
|
std::string relative = asset_path(image_hash, "images");
|
|
Lardon3DProjectDbImageRegisterStatus status{};
|
|
CHECK(lardon3d_project_db_register_image(
|
|
fixture->state.project_db, fixture->scanset.scanset_id, image_hash,
|
|
relative.c_str(), 1, "synthetic", relative.c_str(), 0, 1, &status,
|
|
&fixture->images[camera]) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
std::vector<Lardon3DFeatureKeypoint> keypoints(kTrackCount);
|
|
std::vector<unsigned char> descriptors(kTrackCount * 32);
|
|
for (size_t track = 0; track < kTrackCount; ++track) {
|
|
auto pixel = project(geometry, camera, points[track]);
|
|
keypoints[track].x = static_cast<float>(pixel.x);
|
|
keypoints[track].y = static_cast<float>(pixel.y);
|
|
if (camera == kImageCount - 1 && track == 0)
|
|
keypoints[track].x += 0.25F;
|
|
keypoints[track].size = 1.0F;
|
|
std::memset(descriptors.data() + track * 32, static_cast<int>(track + camera), 32);
|
|
}
|
|
Lardon3DExtractedFeatures features{};
|
|
features.image_width = 4000;
|
|
features.image_height = 3000;
|
|
features.feature_count = static_cast<uint32_t>(kTrackCount);
|
|
features.keypoints = keypoints.data();
|
|
features.descriptors = descriptors.data();
|
|
features.descriptor_bytes = descriptors.size();
|
|
CHECK(lardon3d_feature_store_publish_v2(
|
|
&fixture->state, fixture->images[camera].image_id, 0, "test", 1,
|
|
feature_fingerprint, LARDON3D_FEATURE_DESCRIPTOR_U8, 32, 0, &features,
|
|
&fixture->feature_sets[camera]) == LARDON3D_FEATURE_STORE_OK);
|
|
}
|
|
|
|
Lardon3DSparseCalibration calibration{};
|
|
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(fixture->state.project_db, &calibration, &stored) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
Lardon3DSparseCalibrationMember members[kImageCount]{};
|
|
for (size_t camera = 0; camera < kImageCount; ++camera) {
|
|
members[camera].image_id = fixture->images[camera].image_id;
|
|
members[camera].calibration_id = stored.calibration_id;
|
|
std::memcpy(members[camera].calibration_hash, stored.scientific_hash, 32);
|
|
}
|
|
CHECK(lardon3d_sparse_calibration_scope_create(fixture->state.project_db, members, kImageCount,
|
|
&fixture->scope) == LARDON3D_PROJECT_DB_OK);
|
|
|
|
std::vector<Lardon3DProjectDbTrackObservation> observations(kTrackCount * kImageCount);
|
|
std::vector<Lardon3DProjectDbTrack> tracks(kTrackCount);
|
|
for (size_t track = 0; track < kTrackCount; ++track) {
|
|
tracks[track].observation_count = kImageCount;
|
|
tracks[track].observations = observations.data() + track * kImageCount;
|
|
for (size_t camera = 0; camera < kImageCount; ++camera)
|
|
tracks[track].observations[camera] = {
|
|
fixture->feature_sets[camera].feature_set_id, static_cast<uint32_t>(track),
|
|
static_cast<uint32_t>(camera)};
|
|
}
|
|
Lardon3DProjectDbTrackSet configuration{};
|
|
std::snprintf(configuration.builder_kind, sizeof(configuration.builder_kind), "test");
|
|
configuration.builder_version = 1;
|
|
configuration.verifier_kind = 1;
|
|
configuration.verifier_version = 1;
|
|
configuration.gvr_count = 1;
|
|
configuration.track_count = kTrackCount;
|
|
CHECK(lardon3d_project_db_create_track_set(fixture->state.project_db, &configuration,
|
|
tracks.data(), tracks.size(),
|
|
&fixture->track_set) == LARDON3D_PROJECT_DB_OK);
|
|
std::vector<Lardon3DProjectDbTrackObservation> base_observations(
|
|
kTrackCount * kBaseImageCount);
|
|
std::vector<Lardon3DProjectDbTrack> base_tracks(kTrackCount);
|
|
for (size_t track = 0; track < kTrackCount; ++track) {
|
|
base_tracks[track].observation_count = kBaseImageCount;
|
|
base_tracks[track].observations =
|
|
base_observations.data() + track * kBaseImageCount;
|
|
for (size_t camera = 0; camera < kBaseImageCount; ++camera)
|
|
base_tracks[track].observations[camera] = {
|
|
fixture->feature_sets[camera].feature_set_id,
|
|
static_cast<uint32_t>(track), static_cast<uint32_t>(camera)};
|
|
}
|
|
configuration.parameter_fingerprint[0] = 1;
|
|
CHECK(lardon3d_project_db_create_track_set(
|
|
fixture->state.project_db, &configuration, base_tracks.data(),
|
|
base_tracks.size(), &fixture->base_track_set) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
CHECK(lardon3d_sparse_incremental_parameters_default(&fixture->parameters));
|
|
fixture->parameters.minimum_seed_tracks = 6;
|
|
fixture->parameters.minimum_seed_landmarks = 6;
|
|
fixture->parameters.minimum_pnp_correspondences = 6;
|
|
fixture->parameters.maximum_registration_rounds = 8;
|
|
return true;
|
|
}
|
|
|
|
void destroy_runtime(Fixture *fixture) {
|
|
if (fixture->state.task_queue) lardon3d_task_queue_destroy(fixture->state.task_queue);
|
|
if (fixture->state.resource_governor)
|
|
lardon3d_resource_governor_destroy(fixture->state.resource_governor);
|
|
if (fixture->state.project_db) lardon3d_project_db_close(fixture->state.project_db);
|
|
fixture->state = {};
|
|
}
|
|
|
|
Lardon3DSparseSfmTaskConfiguration configuration(Fixture *fixture) {
|
|
return {fixture->base_track_set.track_set_id, fixture->scope.scope_id,
|
|
fixture->parameters};
|
|
}
|
|
|
|
bool recompute_metrics(Fixture *fixture,
|
|
const Lardon3DSparseReconstruction &reconstruction,
|
|
double *rmse, double *median) {
|
|
std::map<uint64_t, Lardon3DSparseRegisteredImage> cameras;
|
|
std::map<uint64_t, Lardon3DSparseLandmark> landmarks;
|
|
Lardon3DSparseRegisteredImage camera_items[64]{};
|
|
Lardon3DSparseRegisteredImagePage camera_page{0, 64, 0, 0, camera_items};
|
|
CHECK(lardon3d_sparse_registered_image_list(
|
|
fixture->state.project_db, reconstruction.reconstruction_id, 0,
|
|
64, &camera_page) == LARDON3D_PROJECT_DB_OK);
|
|
for (size_t index = 0; index < camera_page.count; ++index)
|
|
cameras.emplace(camera_items[index].image_id, camera_items[index]);
|
|
Lardon3DSparseLandmark landmark_items[64]{};
|
|
Lardon3DSparseLandmarkPage landmark_page{0, 64, 0, 0, landmark_items};
|
|
CHECK(lardon3d_sparse_landmark_list(
|
|
fixture->state.project_db, reconstruction.reconstruction_id, 0,
|
|
64, &landmark_page) == LARDON3D_PROJECT_DB_OK);
|
|
for (size_t index = 0; index < landmark_page.count; ++index)
|
|
landmarks.emplace(landmark_items[index].track_id, landmark_items[index]);
|
|
Lardon3DSparseLandmarkObservation observation_items[64]{};
|
|
Lardon3DSparseObservationPage observation_page{0, 0, 64, 0, 0, 0,
|
|
observation_items};
|
|
CHECK(lardon3d_sparse_observation_list(
|
|
fixture->state.project_db, reconstruction.reconstruction_id, 0, 0,
|
|
64, &observation_page) == LARDON3D_PROJECT_DB_OK);
|
|
std::vector<double> errors;
|
|
double sum = 0.0;
|
|
const auto geometry = Lardon3DSparseGeometryCalibration{
|
|
4000, 3000, 2000.0, 2000.0, 2000.0, 1500.0, 0.0, 0.0, 0.0, 0.0};
|
|
for (size_t index = 0; index < observation_page.count; ++index) {
|
|
const auto &observation = observation_items[index];
|
|
auto landmark = landmarks.find(observation.track_id);
|
|
const Lardon3DProjectDbFeatureSet *feature_set = nullptr;
|
|
size_t camera_index = 0;
|
|
for (; camera_index < kImageCount; ++camera_index)
|
|
if (fixture->feature_sets[camera_index].feature_set_id ==
|
|
observation.feature_set_id) {
|
|
feature_set = &fixture->feature_sets[camera_index];
|
|
break;
|
|
}
|
|
CHECK(landmark != landmarks.end() && feature_set != nullptr);
|
|
auto camera = cameras.find(fixture->images[camera_index].image_id);
|
|
CHECK(camera != cameras.end());
|
|
Lardon3DFeatureReader *reader = nullptr;
|
|
Lardon3DFeatureFileMetadata metadata{};
|
|
CHECK(lardon3d_feature_reader_open(fixture->root.c_str(), feature_set,
|
|
&reader, &metadata) ==
|
|
LARDON3D_FEATURE_STORE_OK);
|
|
Lardon3DFeatureKeypoint keypoint{};
|
|
const auto read_status = lardon3d_feature_reader_keypoints(
|
|
reader, observation.feature_index, &keypoint, 1);
|
|
lardon3d_feature_reader_close(reader);
|
|
CHECK(read_status == LARDON3D_FEATURE_STORE_OK);
|
|
const double *r = camera->second.rotation_cw;
|
|
const double *t = camera->second.translation_cw;
|
|
const double x = r[0] * landmark->second.x + r[1] * landmark->second.y +
|
|
r[2] * landmark->second.z + t[0];
|
|
const double y = r[3] * landmark->second.x + r[4] * landmark->second.y +
|
|
r[5] * landmark->second.z + t[1];
|
|
const double z = r[6] * landmark->second.x + r[7] * landmark->second.y +
|
|
r[8] * landmark->second.z + t[2];
|
|
CHECK(std::isfinite(z) && z > 1e-9);
|
|
const double dx = geometry.fx * x / z + geometry.cx - keypoint.x;
|
|
const double dy = geometry.fy * y / z + geometry.cy - keypoint.y;
|
|
const double squared = dx * dx + dy * dy;
|
|
CHECK(std::isfinite(squared) && std::isfinite(sum + squared));
|
|
sum += squared;
|
|
errors.push_back(std::sqrt(squared));
|
|
}
|
|
CHECK(!errors.empty());
|
|
std::sort(errors.begin(), errors.end());
|
|
*rmse = std::sqrt(sum / static_cast<double>(errors.size()));
|
|
const size_t middle = errors.size() / 2;
|
|
*median = errors.size() % 2
|
|
? errors[middle]
|
|
: errors[middle - 1] +
|
|
(errors[middle] - errors[middle - 1]) / 2.0;
|
|
return std::isfinite(*rmse) && std::isfinite(*median);
|
|
}
|
|
|
|
bool test_lookup() {
|
|
Lardon3DSparseIncrementalObservation values[3] = {
|
|
{1, 10, 100, 1, 8, 0, 0}, {2, 20, 100, 2, 8, 0, 0},
|
|
{3, 30, 200, 1, 8, 0, 0}};
|
|
size_t index = 99;
|
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 10, 100, 1,
|
|
&index) == 1 && index == 0);
|
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 2, 20, 100, 2,
|
|
&index) == 1 && index == 1);
|
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 3, 30, 200, 1,
|
|
&index) == 1 && index == 2);
|
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 10, 999, 1,
|
|
&index) == 0);
|
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 99, 10, 100, 1,
|
|
&index) == 0);
|
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 99, 100, 1,
|
|
&index) == 0);
|
|
values[2].feature_set_id = 100;
|
|
CHECK(lardon3d_sparse_sfm_task_test_observation_lookup(values, 3, 1, 10, 100, 1,
|
|
&index) == -1);
|
|
return true;
|
|
}
|
|
|
|
bool test_landmark_projection_order() {
|
|
Lardon3DSparseIncrementalLandmark landmarks[5]{};
|
|
const uint64_t component_keys[2] = {5, 10};
|
|
const uint64_t input_components[5] = {10, 5, 99, 5, 10};
|
|
const uint64_t input_tracks[5] = {2, 100, 3, 7, 1};
|
|
for (size_t index = 0; index < 5; ++index) {
|
|
landmarks[index].component_key = input_components[index];
|
|
landmarks[index].track_id = input_tracks[index];
|
|
landmarks[index].point.z = 1.0;
|
|
landmarks[index].observation_count = 2;
|
|
}
|
|
Lardon3DSparseLandmark projected[5]{};
|
|
size_t count = 0;
|
|
CHECK(lardon3d_sparse_sfm_task_test_project_landmarks(
|
|
landmarks, 5, component_keys, 2, projected, 5, &count));
|
|
CHECK(count == 4);
|
|
CHECK(projected[0].component_key == 5 && projected[0].track_id == 7);
|
|
CHECK(projected[1].component_key == 5 && projected[1].track_id == 100);
|
|
CHECK(projected[2].component_key == 10 && projected[2].track_id == 1);
|
|
CHECK(projected[3].component_key == 10 && projected[3].track_id == 2);
|
|
for (size_t index = 0; index < count; ++index)
|
|
CHECK(projected[index].component_key != 99);
|
|
return true;
|
|
}
|
|
|
|
bool test_task() {
|
|
Fixture fixture;
|
|
CHECK(create_fixture(&fixture));
|
|
auto config = configuration(&fixture);
|
|
uint64_t task_id = 0;
|
|
Lardon3DTask *task = lardon3d_project_create_sparse_sfm_task(&fixture.state, &config, &task_id);
|
|
CHECK(task && task_id != 0);
|
|
char kind[LARDON3D_TASK_KIND_CAPACITY]{};
|
|
uint32_t kind_version = 0;
|
|
CHECK(lardon3d_task_kind(task, kind, &kind_version));
|
|
CHECK(std::strcmp(kind, LARDON3D_SPARSE_SFM_TASK_KIND) == 0 && kind_version == 1);
|
|
Lardon3DResourceEstimate estimate{};
|
|
CHECK(lardon3d_task_resource_estimate(task, &estimate));
|
|
CHECK(estimate.memory_fixed_bytes == 135266304 && estimate.desired_cpu_threads == 1 &&
|
|
estimate.desired_io_slots == 1 && estimate.minimum_batch_size == 1 &&
|
|
estimate.maximum_batch_size == 1);
|
|
Lardon3DProjectDbSparseSfmTask durable{};
|
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(fixture.state.project_db, task_id, &durable) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, task, nullptr));
|
|
CHECK(wait_state(fixture.state.task_queue, task_id, TASK_COMPLETED));
|
|
|
|
unsigned char fingerprint[32]{};
|
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&fixture.parameters, fingerprint));
|
|
Lardon3DSparseReconstruction reconstruction{};
|
|
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
|
fixture.state.project_db, fixture.base_track_set.track_set_id,
|
|
fixture.scope.scope_id, 1, 1,
|
|
fingerprint, &reconstruction) == LARDON3D_PROJECT_DB_OK);
|
|
CHECK(reconstruction.component_count == 1 && reconstruction.registered_image_count == 3 &&
|
|
reconstruction.landmark_count >= 6 &&
|
|
std::isfinite(reconstruction.reprojection_rmse_px) &&
|
|
std::isfinite(reconstruction.reprojection_median_px));
|
|
Lardon3DSparseLandmarkObservation persisted_observations[64]{};
|
|
Lardon3DSparseObservationPage observation_page{};
|
|
observation_page.items = persisted_observations;
|
|
CHECK(lardon3d_sparse_observation_list(fixture.state.project_db,
|
|
reconstruction.reconstruction_id, 0, 0, 64,
|
|
&observation_page) == LARDON3D_PROJECT_DB_OK);
|
|
CHECK(observation_page.count >= 18);
|
|
|
|
Lardon3DIncrementalReconstructionTaskConfiguration h_configuration{
|
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
|
fixture.scope.scope_id};
|
|
uint64_t h_task_id = 0;
|
|
Lardon3DTask *h_task = lardon3d_project_create_incremental_reconstruction_task(
|
|
&fixture.state, &h_configuration, &h_task_id);
|
|
CHECK(h_task && h_task_id != 0);
|
|
CHECK(lardon3d_task_kind(h_task, kind, &kind_version));
|
|
CHECK(std::strcmp(kind, LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND) == 0 &&
|
|
kind_version == 1);
|
|
Lardon3DResourceEstimate h_estimate{};
|
|
CHECK(lardon3d_task_resource_estimate(h_task, &h_estimate));
|
|
CHECK(h_estimate.minimum_batch_size == 1 && h_estimate.maximum_batch_size == 1 &&
|
|
h_estimate.desired_cpu_threads == 1 && h_estimate.desired_gpu_slots == 0 &&
|
|
h_estimate.desired_io_slots == 1 &&
|
|
h_estimate.task_class == LARDON3D_RESOURCE_TASK_CPU);
|
|
Lardon3DProjectDbIncrementalReconstructionTask h_payload{};
|
|
CHECK(lardon3d_project_db_load_incremental_reconstruction_task(
|
|
fixture.state.project_db, h_task_id, &h_payload) == LARDON3D_PROJECT_DB_OK);
|
|
unsigned char h_fingerprint[32]{};
|
|
CHECK(lardon3d_incremental_reconstruction_parameter_fingerprint(h_fingerprint));
|
|
CHECK(std::memcmp(h_payload.parameter_fingerprint, h_fingerprint, 32) == 0);
|
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, h_task, nullptr));
|
|
CHECK(wait_state(fixture.state.task_queue, h_task_id, TASK_COMPLETED));
|
|
CHECK(wait_reservations_released(fixture.state.resource_governor));
|
|
Lardon3DIncrementalReconstructionIdentity h_identity{
|
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
|
fixture.scope.scope_id, 1, 1, {}};
|
|
std::memcpy(h_identity.parameter_fingerprint, h_fingerprint, 32);
|
|
unsigned char h_digest[32]{};
|
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(&h_identity,
|
|
h_digest));
|
|
Lardon3DIncrementalReconstructionMetadata h_metadata{};
|
|
Lardon3DSparseReconstruction h_reconstruction{};
|
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
|
fixture.state.project_db, h_digest, &h_metadata,
|
|
&h_reconstruction) == LARDON3D_PROJECT_DB_OK);
|
|
CHECK(h_metadata.base_reconstruction_id == reconstruction.reconstruction_id);
|
|
CHECK(h_reconstruction.reconstruction_id != reconstruction.reconstruction_id &&
|
|
h_reconstruction.registered_image_count == kImageCount &&
|
|
h_reconstruction.landmark_count == reconstruction.landmark_count &&
|
|
h_reconstruction.reprojection_rmse_px > 0.0 &&
|
|
h_reconstruction.reprojection_median_px >= 0.0);
|
|
double expected_h_rmse = 0.0;
|
|
double expected_h_median = 0.0;
|
|
CHECK(recompute_metrics(&fixture, h_reconstruction, &expected_h_rmse,
|
|
&expected_h_median));
|
|
CHECK(std::abs(h_reconstruction.reprojection_rmse_px - expected_h_rmse) <
|
|
1e-12 &&
|
|
std::abs(h_reconstruction.reprojection_median_px - expected_h_median) <
|
|
1e-12);
|
|
CHECK(std::abs(h_reconstruction.reprojection_rmse_px -
|
|
reconstruction.reprojection_rmse_px) > 1e-12);
|
|
|
|
Lardon3DSparseCalibration incompatible_calibration{};
|
|
incompatible_calibration.model_kind =
|
|
LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE;
|
|
incompatible_calibration.model_version =
|
|
LARDON3D_SPARSE_SFM_CALIBRATION_VERSION;
|
|
incompatible_calibration.width = 4000;
|
|
incompatible_calibration.height = 3000;
|
|
incompatible_calibration.fx = 2100.0;
|
|
incompatible_calibration.fy = 2100.0;
|
|
incompatible_calibration.cx = 2000.0;
|
|
incompatible_calibration.cy = 1500.0;
|
|
incompatible_calibration.provenance_kind =
|
|
LARDON3D_SPARSE_SFM_PROVENANCE_USER_EXPLICIT;
|
|
Lardon3DSparseCalibration incompatible_stored{};
|
|
CHECK(lardon3d_sparse_calibration_create(
|
|
fixture.state.project_db, &incompatible_calibration,
|
|
&incompatible_stored) == LARDON3D_PROJECT_DB_OK);
|
|
Lardon3DSparseCalibrationMember incompatible_members[kImageCount]{};
|
|
for (size_t index = 0; index < kImageCount; ++index) {
|
|
incompatible_members[index].image_id = fixture.images[index].image_id;
|
|
incompatible_members[index].calibration_id = incompatible_stored.calibration_id;
|
|
std::memcpy(incompatible_members[index].calibration_hash,
|
|
incompatible_stored.scientific_hash, 32);
|
|
}
|
|
Lardon3DSparseCalibrationScope incompatible_scope{};
|
|
CHECK(lardon3d_sparse_calibration_scope_create(
|
|
fixture.state.project_db, incompatible_members, kImageCount,
|
|
&incompatible_scope) == LARDON3D_PROJECT_DB_OK);
|
|
Lardon3DIncrementalReconstructionTaskConfiguration incompatible_h{
|
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
|
incompatible_scope.scope_id};
|
|
uint64_t incompatible_task_id = 0;
|
|
CHECK(lardon3d_project_enqueue_incremental_reconstruction_task(
|
|
&fixture.state, &incompatible_h, &incompatible_task_id));
|
|
CHECK(wait_state(fixture.state.task_queue, incompatible_task_id, TASK_FAILED));
|
|
CHECK(wait_reservations_released(fixture.state.resource_governor));
|
|
Lardon3DIncrementalReconstructionIdentity incompatible_identity{
|
|
reconstruction.reconstruction_id, fixture.track_set.track_set_id,
|
|
incompatible_scope.scope_id, 1, 1, {}};
|
|
std::memcpy(incompatible_identity.parameter_fingerprint, h_fingerprint, 32);
|
|
unsigned char incompatible_digest[32]{};
|
|
CHECK(lardon3d_incremental_reconstruction_identity_digest(
|
|
&incompatible_identity, incompatible_digest));
|
|
Lardon3DIncrementalReconstructionMetadata missing_metadata{};
|
|
Lardon3DSparseReconstruction missing_reconstruction{};
|
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
|
fixture.state.project_db, incompatible_digest, &missing_metadata,
|
|
&missing_reconstruction) == LARDON3D_PROJECT_DB_NOT_FOUND);
|
|
|
|
uint64_t h_restart_id = 0;
|
|
Lardon3DTask *h_pending =
|
|
lardon3d_project_create_incremental_reconstruction_task(
|
|
&fixture.state, &h_configuration, &h_restart_id);
|
|
CHECK(h_pending != nullptr);
|
|
Lardon3DTaskDurableSnapshot h_before{};
|
|
CHECK(lardon3d_task_durable_snapshot(h_pending, &h_before));
|
|
lardon3d_task_destroy(h_pending);
|
|
Lardon3DTaskKindDescriptor h_descriptor{
|
|
LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND, 1,
|
|
lardon3d_incremental_reconstruction_task_reconstruct};
|
|
Lardon3DTaskKindRegistry h_registry{};
|
|
CHECK(lardon3d_task_kind_registry_init(&h_registry, &h_descriptor, 1));
|
|
Lardon3DTaskReconstructionContext h_runtime{
|
|
fixture.root.c_str(), fixture.state.project_db,
|
|
fixture.state.resource_governor, nullptr};
|
|
Lardon3DTask *h_restored = nullptr;
|
|
CHECK(lardon3d_task_kind_registry_restore(
|
|
&h_registry, LARDON3D_INCREMENTAL_RECONSTRUCTION_TASK_KIND, 1,
|
|
&h_before, &h_runtime, &h_restored) == LARDON3D_TASK_KIND_OK);
|
|
Lardon3DResourceEstimate h_restored_estimate{};
|
|
CHECK(lardon3d_task_resource_estimate(h_restored, &h_restored_estimate));
|
|
CHECK(std::memcmp(&h_estimate, &h_restored_estimate,
|
|
sizeof(h_estimate)) == 0);
|
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, h_restored, nullptr));
|
|
CHECK(wait_state(fixture.state.task_queue, h_restart_id, TASK_COMPLETED));
|
|
CHECK(wait_reservations_released(fixture.state.resource_governor));
|
|
Lardon3DIncrementalReconstructionMetadata reused_h_metadata{};
|
|
Lardon3DSparseReconstruction reused_h{};
|
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
|
fixture.state.project_db, h_digest, &reused_h_metadata, &reused_h) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
CHECK(reused_h.reconstruction_id == h_reconstruction.reconstruction_id);
|
|
|
|
uint64_t reuse_id = 0;
|
|
CHECK(lardon3d_project_enqueue_sparse_sfm_task(&fixture.state, &config, &reuse_id));
|
|
CHECK(wait_state(fixture.state.task_queue, reuse_id, TASK_COMPLETED));
|
|
Lardon3DSparseReconstruction reused{};
|
|
CHECK(lardon3d_sparse_reconstruction_find_exact(
|
|
fixture.state.project_db, fixture.base_track_set.track_set_id,
|
|
fixture.scope.scope_id, 1, 1,
|
|
fingerprint, &reused) == LARDON3D_PROJECT_DB_OK);
|
|
CHECK(reused.reconstruction_id == reconstruction.reconstruction_id);
|
|
|
|
uint64_t restart_id = 0;
|
|
Lardon3DTask *pending =
|
|
lardon3d_project_create_sparse_sfm_task(&fixture.state, &config, &restart_id);
|
|
CHECK(pending != nullptr);
|
|
Lardon3DTaskDurableSnapshot before{};
|
|
CHECK(lardon3d_task_durable_snapshot(pending, &before));
|
|
lardon3d_task_destroy(pending);
|
|
lardon3d_project_db_close(fixture.state.project_db);
|
|
fixture.state.project_db = nullptr;
|
|
char error[LARDON3D_PROJECT_DB_ERROR_CAPACITY]{};
|
|
CHECK(lardon3d_project_db_open(fixture.database_path.c_str(), &fixture.state.project_db, error) ==
|
|
LARDON3D_PROJECT_DB_OK);
|
|
Lardon3DIncrementalReconstructionMetadata reopened_h_metadata{};
|
|
Lardon3DSparseReconstruction reopened_h{};
|
|
CHECK(lardon3d_incremental_reconstruction_find_exact(
|
|
fixture.state.project_db, h_digest, &reopened_h_metadata,
|
|
&reopened_h) == LARDON3D_PROJECT_DB_OK);
|
|
CHECK(reopened_h.reconstruction_id == h_reconstruction.reconstruction_id &&
|
|
reopened_h_metadata.base_reconstruction_id ==
|
|
reconstruction.reconstruction_id &&
|
|
reopened_h.registered_image_count == kImageCount &&
|
|
reopened_h.reprojection_rmse_px ==
|
|
h_reconstruction.reprojection_rmse_px);
|
|
Lardon3DTaskKindDescriptor descriptor{LARDON3D_SPARSE_SFM_TASK_KIND, 1,
|
|
lardon3d_sparse_sfm_task_reconstruct};
|
|
Lardon3DTaskKindRegistry registry{};
|
|
CHECK(lardon3d_task_kind_registry_init(®istry, &descriptor, 1));
|
|
Lardon3DTaskReconstructionContext runtime{fixture.root.c_str(), fixture.state.project_db,
|
|
fixture.state.resource_governor, nullptr};
|
|
Lardon3DTask *restored = nullptr;
|
|
CHECK(lardon3d_task_kind_registry_restore(®istry, LARDON3D_SPARSE_SFM_TASK_KIND, 1,
|
|
&before, &runtime, &restored) ==
|
|
LARDON3D_TASK_KIND_OK);
|
|
Lardon3DResourceEstimate restored_estimate{};
|
|
CHECK(lardon3d_task_resource_estimate(restored, &restored_estimate));
|
|
CHECK(std::memcmp(&estimate, &restored_estimate, sizeof(estimate)) == 0);
|
|
Lardon3DProjectDbSparseSfmTask restored_payload{};
|
|
CHECK(lardon3d_project_db_load_sparse_sfm_task(fixture.state.project_db, restart_id,
|
|
&restored_payload) == LARDON3D_PROJECT_DB_OK);
|
|
unsigned char restored_fingerprint[32]{};
|
|
CHECK(lardon3d_sparse_sfm_parameter_fingerprint(&restored_payload.parameters,
|
|
restored_fingerprint));
|
|
CHECK(std::memcmp(fingerprint, restored_fingerprint, 32) == 0);
|
|
unsigned char record_before[372]{};
|
|
unsigned char record_after[372]{};
|
|
CHECK(lardon3d_sparse_sfm_fingerprint_record(&fixture.parameters, record_before));
|
|
CHECK(lardon3d_sparse_sfm_fingerprint_record(&restored_payload.parameters, record_after));
|
|
CHECK(std::memcmp(record_before, record_after, sizeof(record_before)) == 0);
|
|
CHECK(lardon3d_task_queue_add(fixture.state.task_queue, restored, nullptr));
|
|
CHECK(wait_state(fixture.state.task_queue, restart_id, TASK_COMPLETED));
|
|
destroy_runtime(&fixture);
|
|
return true;
|
|
}
|
|
} // namespace
|
|
|
|
int main() {
|
|
return test_lookup() && test_landmark_projection_order() && test_task() ? 0 : 1;
|
|
}
|