1730 lines
73 KiB
C
1730 lines
73 KiB
C
#include <limits.h>
|
|
#include <math.h>
|
|
#include <openssl/evp.h>
|
|
#include <pthread.h>
|
|
#include <sqlite3.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "project_db_internal.h"
|
|
#include <lardon3d/sparse_sfm_model.h>
|
|
|
|
static bool sparse_finite(double value) { return isfinite(value) != 0; }
|
|
|
|
static bool sparse_fits_sqlite(uint64_t value) {
|
|
return value <= (uint64_t)INT64_MAX;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult sparse_step_reuse(Lardon3DProjectDb *db,
|
|
sqlite3_stmt *statement,
|
|
const char *context) {
|
|
int code = sqlite3_step(statement);
|
|
if (code != SQLITE_DONE)
|
|
return sqlite_result(db, code, context);
|
|
sqlite3_reset(statement);
|
|
sqlite3_clear_bindings(statement);
|
|
return LARDON3D_PROJECT_DB_OK;
|
|
}
|
|
|
|
static void sparse_put_u32(unsigned char *data, size_t *offset,
|
|
uint32_t value) {
|
|
for (unsigned byte = 0; byte < 4; ++byte)
|
|
data[(*offset)++] = (unsigned char)(value >> (byte * 8));
|
|
}
|
|
|
|
static void sparse_put_u64(unsigned char *data, size_t *offset,
|
|
uint64_t value);
|
|
|
|
static bool sparse_incremental_identity_valid(
|
|
const Lardon3DIncrementalReconstructionMetadata *metadata) {
|
|
unsigned char record[76] = {0};
|
|
unsigned char digest[32] = {0};
|
|
unsigned int digest_size = 0;
|
|
size_t offset = 0;
|
|
if (!metadata || metadata->base_reconstruction_id == 0 ||
|
|
metadata->extension_track_set_id == 0 ||
|
|
metadata->calibration_scope_id == 0 || metadata->incremental_kind != 1 ||
|
|
metadata->incremental_version != 1)
|
|
return false;
|
|
memcpy(record, "L3DHIDV1", 8);
|
|
offset = 8;
|
|
sparse_put_u32(record, &offset, 1);
|
|
sparse_put_u64(record, &offset, metadata->base_reconstruction_id);
|
|
sparse_put_u64(record, &offset, metadata->extension_track_set_id);
|
|
sparse_put_u64(record, &offset, metadata->calibration_scope_id);
|
|
sparse_put_u32(record, &offset, metadata->incremental_kind);
|
|
sparse_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), digest, &digest_size, EVP_sha256(),
|
|
NULL) == 1 &&
|
|
digest_size == sizeof(digest) &&
|
|
memcmp(digest, metadata->scientific_identity, sizeof(digest)) == 0;
|
|
}
|
|
|
|
static void sparse_put_u64(unsigned char *data, size_t *offset,
|
|
uint64_t value) {
|
|
for (unsigned byte = 0; byte < 8; ++byte)
|
|
data[(*offset)++] = (unsigned char)(value >> (byte * 8));
|
|
}
|
|
|
|
static void sparse_put_double(unsigned char *data, size_t *offset,
|
|
double value) {
|
|
uint64_t bits = 0;
|
|
if (value == 0.0)
|
|
value = 0.0;
|
|
memcpy(&bits, &value, sizeof(bits));
|
|
sparse_put_u64(data, offset, bits);
|
|
}
|
|
|
|
static bool sparse_hash(const unsigned char *data, size_t size,
|
|
unsigned char hash[32]) {
|
|
unsigned int length = 0;
|
|
return EVP_Digest(data, size, hash, &length, EVP_sha256(), NULL) == 1 &&
|
|
length == 32;
|
|
}
|
|
|
|
static bool sparse_calibration_valid(const Lardon3DSparseCalibration *input) {
|
|
if (!input ||
|
|
input->model_kind != LARDON3D_SPARSE_SFM_CALIBRATION_KIND_PINHOLE ||
|
|
input->model_version != 1 || input->width == 0 || input->height == 0 ||
|
|
input->provenance_kind < LARDON3D_SPARSE_SFM_PROVENANCE_USER_EXPLICIT ||
|
|
input->provenance_kind >
|
|
LARDON3D_SPARSE_SFM_PROVENANCE_IMPORTED_TRUSTED ||
|
|
!(input->fx > 0.0) || !(input->fy > 0.0) || input->cx < 0.0 ||
|
|
input->cy < 0.0 || input->cx >= input->width ||
|
|
input->cy >= input->height)
|
|
return false;
|
|
const double values[] = {input->fx, input->fy, input->cx, input->cy,
|
|
input->k1, input->k2, input->p1, input->p2};
|
|
for (size_t index = 0; index < sizeof(values) / sizeof(values[0]); ++index)
|
|
if (!sparse_finite(values[index]))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static bool sparse_calibration_hash(const Lardon3DSparseCalibration *input,
|
|
unsigned char hash[32]) {
|
|
if (!sparse_calibration_valid(input))
|
|
return false;
|
|
unsigned char bytes[8 + 4 * 6 + 8 * 8 + 4 + 32];
|
|
size_t offset = 0;
|
|
memcpy(bytes + offset, "L3D3DCP1", 8);
|
|
offset += 8;
|
|
sparse_put_u32(bytes, &offset, 1);
|
|
sparse_put_u32(bytes, &offset, input->model_kind);
|
|
sparse_put_u32(bytes, &offset, input->model_version);
|
|
sparse_put_u32(bytes, &offset, input->width);
|
|
sparse_put_u32(bytes, &offset, input->height);
|
|
const double values[] = {input->fx, input->fy, input->cx, input->cy,
|
|
input->k1, input->k2, input->p1, input->p2};
|
|
for (size_t index = 0; index < sizeof(values) / sizeof(values[0]); ++index)
|
|
sparse_put_double(bytes, &offset, values[index]);
|
|
sparse_put_u32(bytes, &offset, input->provenance_kind);
|
|
memcpy(bytes + offset, input->provenance_fingerprint, 32);
|
|
offset += 32;
|
|
return sparse_hash(bytes, offset, hash);
|
|
}
|
|
|
|
static Lardon3DProjectDbResult
|
|
sparse_load_calibration_locked(Lardon3DProjectDb *db, uint64_t id,
|
|
Lardon3DSparseCalibration *output) {
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT "
|
|
"scientific_hash,model_kind,model_version,width,height,fx,fy,cx,"
|
|
"cy,k1,k2,p1,p2,"
|
|
"provenance_kind,provenance_fingerprint FROM sparse_calibrations "
|
|
"WHERE calibration_id=?1",
|
|
&statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
return result;
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 0) != 32 ||
|
|
sqlite3_column_bytes(statement, 14) != 32)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else {
|
|
memset(output, 0, sizeof(*output));
|
|
output->calibration_id = id;
|
|
memcpy(output->scientific_hash, sqlite3_column_blob(statement, 0), 32);
|
|
output->model_kind = (uint32_t)sqlite3_column_int64(statement, 1);
|
|
output->model_version = (uint32_t)sqlite3_column_int64(statement, 2);
|
|
output->width = (uint32_t)sqlite3_column_int64(statement, 3);
|
|
output->height = (uint32_t)sqlite3_column_int64(statement, 4);
|
|
output->fx = sqlite3_column_double(statement, 5);
|
|
output->fy = sqlite3_column_double(statement, 6);
|
|
output->cx = sqlite3_column_double(statement, 7);
|
|
output->cy = sqlite3_column_double(statement, 8);
|
|
output->k1 = sqlite3_column_double(statement, 9);
|
|
output->k2 = sqlite3_column_double(statement, 10);
|
|
output->p1 = sqlite3_column_double(statement, 11);
|
|
output->p2 = sqlite3_column_double(statement, 12);
|
|
output->provenance_kind = (uint32_t)sqlite3_column_int64(statement, 13);
|
|
memcpy(output->provenance_fingerprint, sqlite3_column_blob(statement, 14),
|
|
32);
|
|
unsigned char expected[32];
|
|
if (!sparse_calibration_hash(output, expected) ||
|
|
memcmp(expected, output->scientific_hash, 32) != 0)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
memset(output, 0, sizeof(*output));
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_calibration_create(Lardon3DProjectDb *db,
|
|
const Lardon3DSparseCalibration *input,
|
|
Lardon3DSparseCalibration *output) {
|
|
if (!db || !sparse_calibration_valid(input) || !output)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
unsigned char hash[32];
|
|
if (!sparse_calibration_hash(input, hash))
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
execute(db, "BEGIN IMMEDIATE", "begin calibration");
|
|
sqlite3_stmt *statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"INSERT OR IGNORE INTO "
|
|
"sparse_calibrations(scientific_hash,model_kind,model_version,width,"
|
|
"height,"
|
|
"fx,fy,cx,cy,k1,k2,p1,p2,provenance_kind,provenance_fingerprint) "
|
|
"VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?14,?15)",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, hash, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 2, input->model_kind);
|
|
sqlite3_bind_int64(statement, 3, input->model_version);
|
|
sqlite3_bind_int64(statement, 4, input->width);
|
|
sqlite3_bind_int64(statement, 5, input->height);
|
|
sqlite3_bind_double(statement, 6, input->fx);
|
|
sqlite3_bind_double(statement, 7, input->fy);
|
|
sqlite3_bind_double(statement, 8, input->cx);
|
|
sqlite3_bind_double(statement, 9, input->cy);
|
|
sqlite3_bind_double(statement, 10, input->k1);
|
|
sqlite3_bind_double(statement, 11, input->k2);
|
|
sqlite3_bind_double(statement, 12, input->p1);
|
|
sqlite3_bind_double(statement, 13, input->p2);
|
|
sqlite3_bind_int64(statement, 14, input->provenance_kind);
|
|
sqlite3_bind_blob(statement, 15, input->provenance_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
result = step_done(db, statement, "insert calibration");
|
|
statement = NULL;
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
uint64_t id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(db,
|
|
"SELECT calibration_id FROM sparse_calibrations WHERE "
|
|
"scientific_hash=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, hash, 32, SQLITE_TRANSIENT);
|
|
if (sqlite3_step(statement) != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else
|
|
id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
sqlite3_finalize(statement);
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = execute(db, "COMMIT", "commit calibration");
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
(void)execute(db, "ROLLBACK", "rollback calibration");
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = sparse_load_calibration_locked(db, id, output);
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_calibration_load(Lardon3DProjectDb *db, uint64_t id,
|
|
Lardon3DSparseCalibration *output) {
|
|
if (!db || !id || !output)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
sparse_load_calibration_locked(db, id, output);
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_calibration_find_by_hash(Lardon3DProjectDb *db,
|
|
const unsigned char hash[32],
|
|
Lardon3DSparseCalibration *output) {
|
|
if (!db || !hash || !output)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT calibration_id FROM sparse_calibrations WHERE scientific_hash=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, hash, 32, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else
|
|
result = sparse_load_calibration_locked(
|
|
db, (uint64_t)sqlite3_column_int64(statement, 0), output);
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
memset(output, 0, sizeof(*output));
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
static int sparse_scope_member_compare(const void *left, const void *right) {
|
|
const Lardon3DSparseCalibrationMember *a = left;
|
|
const Lardon3DSparseCalibrationMember *b = right;
|
|
if (a->image_id < b->image_id)
|
|
return -1;
|
|
if (a->image_id > b->image_id)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
static bool sparse_scope_hash(const Lardon3DSparseCalibrationMember *members,
|
|
size_t count, unsigned char hash[32]) {
|
|
if (!members || count == 0 || count > (SIZE_MAX - 20U) / 40U)
|
|
return false;
|
|
size_t size = 20U + count * 40U;
|
|
unsigned char *bytes = malloc(size);
|
|
if (!bytes)
|
|
return false;
|
|
size_t offset = 0;
|
|
memcpy(bytes, "L3D3DSC1", 8);
|
|
offset = 8;
|
|
sparse_put_u32(bytes, &offset, 1);
|
|
sparse_put_u64(bytes, &offset, count);
|
|
for (size_t index = 0; index < count; ++index) {
|
|
sparse_put_u64(bytes, &offset, members[index].image_id);
|
|
memcpy(bytes + offset, members[index].calibration_hash, 32);
|
|
offset += 32;
|
|
}
|
|
bool ok = sparse_hash(bytes, offset, hash);
|
|
free(bytes);
|
|
return ok;
|
|
}
|
|
|
|
static bool sparse_scope_hash_locked(Lardon3DProjectDb *db, uint64_t scope_id,
|
|
uint64_t member_count,
|
|
unsigned char hash[32]) {
|
|
if (member_count > (uint64_t)INT64_MAX)
|
|
return false;
|
|
EVP_MD_CTX *context = EVP_MD_CTX_new();
|
|
if (!context || EVP_DigestInit_ex(context, EVP_sha256(), NULL) != 1) {
|
|
EVP_MD_CTX_free(context);
|
|
return false;
|
|
}
|
|
unsigned char header[20];
|
|
size_t offset = 0;
|
|
memcpy(header, "L3D3DSC1", 8);
|
|
offset = 8;
|
|
sparse_put_u32(header, &offset, 1);
|
|
sparse_put_u64(header, &offset, member_count);
|
|
bool ok = EVP_DigestUpdate(context, header, sizeof(header)) == 1;
|
|
sqlite3_stmt *statement = NULL;
|
|
if (ok)
|
|
ok = prepare(
|
|
db,
|
|
"SELECT m.image_id,c.scientific_hash FROM "
|
|
"sparse_calibration_scope_images AS m JOIN sparse_calibrations AS c "
|
|
"ON c.calibration_id=m.calibration_id WHERE m.scope_id=?1 "
|
|
"ORDER BY m.image_id",
|
|
&statement) == LARDON3D_PROJECT_DB_OK;
|
|
if (ok) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)scope_id);
|
|
uint64_t count = 0;
|
|
int code = SQLITE_ROW;
|
|
while ((code = sqlite3_step(statement)) == SQLITE_ROW) {
|
|
if (sqlite3_column_bytes(statement, 1) != 32 || count == UINT64_MAX) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
unsigned char member[40];
|
|
offset = 0;
|
|
sparse_put_u64(member, &offset,
|
|
(uint64_t)sqlite3_column_int64(statement, 0));
|
|
memcpy(member + offset, sqlite3_column_blob(statement, 1), 32);
|
|
if (EVP_DigestUpdate(context, member, sizeof(member)) != 1) {
|
|
ok = false;
|
|
break;
|
|
}
|
|
++count;
|
|
}
|
|
if (code != SQLITE_DONE || count != member_count)
|
|
ok = false;
|
|
sqlite3_finalize(statement);
|
|
}
|
|
unsigned int length = 0;
|
|
if (ok)
|
|
ok = EVP_DigestFinal_ex(context, hash, &length) == 1 && length == 32;
|
|
EVP_MD_CTX_free(context);
|
|
return ok;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_sparse_calibration_scope_create(
|
|
Lardon3DProjectDb *db, const Lardon3DSparseCalibrationMember *members,
|
|
size_t member_count, Lardon3DSparseCalibrationScope *output) {
|
|
if (!db || !members || !output || member_count == 0 ||
|
|
member_count > INT64_MAX)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
if (member_count > SIZE_MAX / sizeof(*members))
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
|
|
Lardon3DSparseCalibrationMember *canonical =
|
|
malloc(member_count * sizeof(*canonical));
|
|
if (!canonical)
|
|
return LARDON3D_PROJECT_DB_IO_ERROR;
|
|
memcpy(canonical, members, member_count * sizeof(*canonical));
|
|
qsort(canonical, member_count, sizeof(*canonical),
|
|
sparse_scope_member_compare);
|
|
for (size_t index = 0; index < member_count; ++index) {
|
|
if (!sparse_fits_sqlite(canonical[index].image_id) ||
|
|
canonical[index].image_id == 0 ||
|
|
(index && canonical[index - 1].image_id == canonical[index].image_id)) {
|
|
free(canonical);
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
}
|
|
}
|
|
unsigned char scope_hash[32];
|
|
if (!sparse_scope_hash(canonical, member_count, scope_hash)) {
|
|
free(canonical);
|
|
return LARDON3D_PROJECT_DB_IO_ERROR;
|
|
}
|
|
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
execute(db, "BEGIN IMMEDIATE", "begin calibration scope");
|
|
sqlite3_stmt *statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(db,
|
|
"SELECT scientific_hash FROM sparse_calibrations "
|
|
"WHERE calibration_id=?1",
|
|
&statement);
|
|
for (size_t index = 0;
|
|
result == LARDON3D_PROJECT_DB_OK && index < member_count; ++index) {
|
|
sqlite3_bind_int64(statement, 1,
|
|
(sqlite3_int64)canonical[index].calibration_id);
|
|
if (sqlite3_step(statement) != SQLITE_ROW ||
|
|
sqlite3_column_bytes(statement, 0) != 32 ||
|
|
memcmp(sqlite3_column_blob(statement, 0), canonical[index].calibration_hash,
|
|
32) != 0)
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
sqlite3_reset(statement);
|
|
sqlite3_clear_bindings(statement);
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"INSERT OR IGNORE INTO sparse_calibration_scopes(scientific_hash,"
|
|
"member_count) VALUES(?1,?2)",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, scope_hash, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)member_count);
|
|
result = step_done(db, statement, "insert calibration scope");
|
|
statement = NULL;
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
|
|
uint64_t scope_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(db,
|
|
"SELECT scope_id FROM sparse_calibration_scopes "
|
|
"WHERE scientific_hash=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, scope_hash, 32, SQLITE_TRANSIENT);
|
|
if (sqlite3_step(statement) != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else
|
|
scope_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"INSERT OR IGNORE INTO sparse_calibration_scope_images(scope_id,"
|
|
"image_id,calibration_id) VALUES(?1,?2,?3)",
|
|
&statement);
|
|
for (size_t index = 0;
|
|
result == LARDON3D_PROJECT_DB_OK && index < member_count; ++index) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)scope_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)canonical[index].image_id);
|
|
sqlite3_bind_int64(statement, 3,
|
|
(sqlite3_int64)canonical[index].calibration_id);
|
|
result =
|
|
sparse_step_reuse(db, statement, "insert calibration scope member");
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = execute(db, "COMMIT", "commit calibration scope");
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
(void)execute(db, "ROLLBACK", "rollback calibration scope");
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
memset(output, 0, sizeof(*output));
|
|
output->scope_id = scope_id;
|
|
memcpy(output->scientific_hash, scope_hash, 32);
|
|
output->member_count = member_count;
|
|
}
|
|
free(canonical);
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_calibration_scope_load(Lardon3DProjectDb *db, uint64_t scope_id,
|
|
Lardon3DSparseCalibrationScope *output) {
|
|
if (!db || !scope_id || !output)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT scientific_hash,member_count FROM "
|
|
"sparse_calibration_scopes WHERE scope_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)(sqlite3_int64)scope_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 0) != 32 ||
|
|
sqlite3_column_int64(statement, 1) <= 0)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else {
|
|
memset(output, 0, sizeof(*output));
|
|
output->scope_id = scope_id;
|
|
memcpy(output->scientific_hash, sqlite3_column_blob(statement, 0), 32);
|
|
output->member_count = (uint64_t)sqlite3_column_int64(statement, 1);
|
|
unsigned char expected[32];
|
|
if (!sparse_scope_hash_locked(db, scope_id, output->member_count,
|
|
expected) ||
|
|
memcmp(expected, output->scientific_hash, 32) != 0)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_sparse_calibration_scope_find_by_hash(
|
|
Lardon3DProjectDb *db, const unsigned char hash[32],
|
|
Lardon3DSparseCalibrationScope *output) {
|
|
if (!db || !hash || !output)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT scope_id FROM sparse_calibration_scopes "
|
|
"WHERE scientific_hash=?1",
|
|
&statement);
|
|
uint64_t scope_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, hash, 32, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else
|
|
scope_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
result = prepare(db,
|
|
"SELECT scientific_hash,member_count FROM "
|
|
"sparse_calibration_scopes WHERE scope_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)scope_id);
|
|
if (sqlite3_step(statement) != SQLITE_ROW ||
|
|
sqlite3_column_bytes(statement, 0) != 32 ||
|
|
sqlite3_column_int64(statement, 1) <= 0) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
} else {
|
|
memset(output, 0, sizeof(*output));
|
|
output->scope_id = scope_id;
|
|
memcpy(output->scientific_hash, sqlite3_column_blob(statement, 0), 32);
|
|
output->member_count = (uint64_t)sqlite3_column_int64(statement, 1);
|
|
unsigned char expected[32];
|
|
if (!sparse_scope_hash_locked(db, scope_id, output->member_count,
|
|
expected) ||
|
|
memcmp(expected, output->scientific_hash, 32) != 0)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
memset(output, 0, sizeof(*output));
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_sparse_calibration_scope_list_members(
|
|
Lardon3DProjectDb *db, uint64_t scope_id, uint64_t after_image_id,
|
|
Lardon3DSparseCalibrationMember *items, size_t capacity, size_t *count,
|
|
uint64_t *next_after_image_id) {
|
|
if (!db || !scope_id || !items || !count || !next_after_image_id ||
|
|
capacity > LARDON3D_SPARSE_SFM_PAGE_MAX)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
*count = 0;
|
|
*next_after_image_id = after_image_id;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT image_id,calibration_id FROM sparse_calibration_scope_images "
|
|
"WHERE scope_id=?1 AND image_id>?2 ORDER BY image_id LIMIT ?3",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)(sqlite3_int64)scope_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_image_id);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)(sqlite3_int64)capacity);
|
|
while (*count < capacity && sqlite3_step(statement) == SQLITE_ROW) {
|
|
items[*count].image_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
items[*count].calibration_id =
|
|
(uint64_t)sqlite3_column_int64(statement, 1);
|
|
Lardon3DSparseCalibration calibration;
|
|
result = sparse_load_calibration_locked(db, items[*count].calibration_id,
|
|
&calibration);
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
break;
|
|
memcpy(items[*count].calibration_hash, calibration.scientific_hash, 32);
|
|
*next_after_image_id = items[*count].image_id;
|
|
++*count;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
*count = 0;
|
|
*next_after_image_id = after_image_id;
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool sparse_rotation_valid(const double rotation[9]) {
|
|
if (!rotation)
|
|
return false;
|
|
for (size_t index = 0; index < 9; ++index)
|
|
if (!sparse_finite(rotation[index]))
|
|
return false;
|
|
double determinant =
|
|
rotation[0] * (rotation[4] * rotation[8] - rotation[5] * rotation[7]) -
|
|
rotation[1] * (rotation[3] * rotation[8] - rotation[5] * rotation[6]) +
|
|
rotation[2] * (rotation[3] * rotation[7] - rotation[4] * rotation[6]);
|
|
if (fabs(determinant - 1.0) > 1e-6)
|
|
return false;
|
|
for (size_t row = 0; row < 3; ++row)
|
|
for (size_t column = 0; column < 3; ++column) {
|
|
double dot = 0.0;
|
|
for (size_t axis = 0; axis < 3; ++axis)
|
|
dot += rotation[row * 3 + axis] * rotation[column * 3 + axis];
|
|
if (fabs(dot - (row == column ? 1.0 : 0.0)) > 1e-6)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
sparse_publication_valid(const Lardon3DSparsePublication *publication) {
|
|
if (!publication || !sparse_fits_sqlite(publication->track_set_id) ||
|
|
!sparse_fits_sqlite(publication->calibration_scope_id) ||
|
|
!publication->track_set_id || !publication->calibration_scope_id ||
|
|
publication->sfm_kind != LARDON3D_SPARSE_SFM_KIND_INCREMENTAL ||
|
|
publication->sfm_version != 1 || !publication->components ||
|
|
publication->component_count == 0 || !publication->registered_images ||
|
|
publication->registered_image_count < 2 || !publication->landmarks ||
|
|
publication->landmark_count == 0 ||
|
|
(!publication->observations && publication->observation_count) ||
|
|
!sparse_finite(publication->reprojection_rmse_px) ||
|
|
!sparse_finite(publication->reprojection_median_px))
|
|
return false;
|
|
for (size_t index = 0; index < publication->component_count; ++index) {
|
|
const Lardon3DSparseComponent *component = &publication->components[index];
|
|
if (!sparse_fits_sqlite(component->component_key) ||
|
|
!sparse_fits_sqlite(component->registered_image_count) ||
|
|
!sparse_fits_sqlite(component->landmark_count) ||
|
|
!component->component_key || !component->registered_image_count ||
|
|
!component->landmark_count ||
|
|
(index && publication->components[index - 1].component_key >=
|
|
component->component_key))
|
|
return false;
|
|
}
|
|
for (size_t index = 0; index < publication->registered_image_count; ++index) {
|
|
const Lardon3DSparseRegisteredImage *pose =
|
|
&publication->registered_images[index];
|
|
if (!sparse_fits_sqlite(pose->image_id) ||
|
|
!sparse_fits_sqlite(pose->component_key) || !pose->image_id ||
|
|
!pose->component_key || !sparse_rotation_valid(pose->rotation_cw) ||
|
|
!sparse_finite(pose->translation_cw[0]) ||
|
|
!sparse_finite(pose->translation_cw[1]) ||
|
|
!sparse_finite(pose->translation_cw[2]) ||
|
|
(index &&
|
|
publication->registered_images[index - 1].image_id >= pose->image_id))
|
|
return false;
|
|
}
|
|
if (publication->component_count > SIZE_MAX / sizeof(size_t) / 3U ||
|
|
publication->component_count > SIZE_MAX / sizeof(uint64_t))
|
|
return false;
|
|
size_t *component_image_counts =
|
|
calloc(publication->component_count * 3U, sizeof(size_t));
|
|
if (!component_image_counts)
|
|
return false;
|
|
uint64_t *minimum_image_ids =
|
|
malloc(publication->component_count * sizeof(*minimum_image_ids));
|
|
if (!minimum_image_ids) {
|
|
free(component_image_counts);
|
|
return false;
|
|
}
|
|
for (size_t index = 0; index < publication->component_count; ++index)
|
|
minimum_image_ids[index] = UINT64_MAX;
|
|
bool components_valid = true;
|
|
for (size_t index = 0;
|
|
components_valid && index < publication->registered_image_count;
|
|
++index) {
|
|
size_t low = 0;
|
|
size_t high = publication->component_count;
|
|
uint64_t key = publication->registered_images[index].component_key;
|
|
while (low < high) {
|
|
size_t middle = low + (high - low) / 2;
|
|
if (publication->components[middle].component_key < key)
|
|
low = middle + 1;
|
|
else
|
|
high = middle;
|
|
}
|
|
if (low == publication->component_count ||
|
|
publication->components[low].component_key != key)
|
|
components_valid = false;
|
|
else {
|
|
++component_image_counts[low];
|
|
if (publication->registered_images[index].image_id <
|
|
minimum_image_ids[low])
|
|
minimum_image_ids[low] = publication->registered_images[index].image_id;
|
|
}
|
|
}
|
|
for (size_t index = 0;
|
|
components_valid && index < publication->landmark_count; ++index) {
|
|
size_t low = 0;
|
|
size_t high = publication->component_count;
|
|
uint64_t key = publication->landmarks[index].component_key;
|
|
while (low < high) {
|
|
size_t middle = low + (high - low) / 2;
|
|
if (publication->components[middle].component_key < key)
|
|
low = middle + 1;
|
|
else
|
|
high = middle;
|
|
}
|
|
if (low == publication->component_count ||
|
|
publication->components[low].component_key != key)
|
|
components_valid = false;
|
|
else
|
|
++component_image_counts[publication->component_count + low];
|
|
}
|
|
for (size_t index = 0;
|
|
components_valid && index < publication->component_count; ++index)
|
|
if (component_image_counts[index] !=
|
|
publication->components[index].registered_image_count ||
|
|
component_image_counts[publication->component_count + index] !=
|
|
publication->components[index].landmark_count ||
|
|
minimum_image_ids[index] != publication->components[index].component_key)
|
|
components_valid = false;
|
|
free(minimum_image_ids);
|
|
free(component_image_counts);
|
|
if (!components_valid)
|
|
return false;
|
|
for (size_t index = 0; index < publication->landmark_count; ++index) {
|
|
const Lardon3DSparseLandmark *landmark = &publication->landmarks[index];
|
|
if (!sparse_fits_sqlite(landmark->track_id) ||
|
|
!sparse_fits_sqlite(landmark->component_key) ||
|
|
!sparse_fits_sqlite(landmark->observation_count) ||
|
|
!landmark->track_id || !landmark->component_key ||
|
|
!landmark->observation_count || !sparse_finite(landmark->x) ||
|
|
!sparse_finite(landmark->y) || !sparse_finite(landmark->z) ||
|
|
!sparse_finite(landmark->reprojection_rmse_px) ||
|
|
!sparse_finite(landmark->reprojection_median_px) ||
|
|
(index &&
|
|
publication->landmarks[index - 1].track_id >= landmark->track_id))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static const Lardon3DSparseLandmark *
|
|
sparse_find_landmark(const Lardon3DSparsePublication *publication,
|
|
uint64_t track_id) {
|
|
size_t low = 0;
|
|
size_t high = publication->landmark_count;
|
|
while (low < high) {
|
|
size_t middle = low + (high - low) / 2;
|
|
uint64_t candidate = publication->landmarks[middle].track_id;
|
|
if (candidate == track_id)
|
|
return &publication->landmarks[middle];
|
|
if (candidate < track_id)
|
|
low = middle + 1;
|
|
else
|
|
high = middle;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const Lardon3DSparseComponent *sparse_find_component(
|
|
const Lardon3DSparsePublication *publication, uint64_t component_key) {
|
|
size_t low = 0;
|
|
size_t high = publication->component_count;
|
|
while (low < high) {
|
|
size_t middle = low + (high - low) / 2;
|
|
uint64_t candidate = publication->components[middle].component_key;
|
|
if (candidate == component_key)
|
|
return &publication->components[middle];
|
|
if (candidate < component_key)
|
|
low = middle + 1;
|
|
else
|
|
high = middle;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const Lardon3DSparseRegisteredImage *sparse_find_registered_image(
|
|
const Lardon3DSparsePublication *publication, uint64_t image_id) {
|
|
size_t low = 0;
|
|
size_t high = publication->registered_image_count;
|
|
while (low < high) {
|
|
size_t middle = low + (high - low) / 2;
|
|
uint64_t candidate = publication->registered_images[middle].image_id;
|
|
if (candidate == image_id)
|
|
return &publication->registered_images[middle];
|
|
if (candidate < image_id)
|
|
low = middle + 1;
|
|
else
|
|
high = middle;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult
|
|
sparse_validate_relations(Lardon3DProjectDb *db,
|
|
const Lardon3DSparsePublication *publication) {
|
|
sqlite3_stmt *scope_statement = NULL;
|
|
sqlite3_stmt *track_statement = NULL;
|
|
sqlite3_stmt *observation_statement = NULL;
|
|
sqlite3_stmt *track_component_statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT 1 FROM sparse_calibration_scope_images "
|
|
"WHERE scope_id=?1 AND image_id=?2",
|
|
&scope_statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(db,
|
|
"SELECT 1 FROM tracks WHERE track_set_id=?1 "
|
|
"AND track_id=?2",
|
|
&track_statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result =
|
|
prepare(db,
|
|
"SELECT 1 FROM track_observations WHERE track_set_id=?1 "
|
|
"AND track_id=?2 AND feature_set_id=?3 AND feature_index=?4 "
|
|
"AND position_in_track=?5",
|
|
&observation_statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"SELECT f.image_id FROM track_observations AS t JOIN feature_sets AS f "
|
|
"ON f.feature_set_id=t.feature_set_id WHERE t.track_set_id=?1 AND "
|
|
"t.track_id=?2 ORDER BY t.position_in_track",
|
|
&track_component_statement);
|
|
for (size_t index = 0;
|
|
result == LARDON3D_PROJECT_DB_OK &&
|
|
index < publication->registered_image_count;
|
|
++index) {
|
|
const Lardon3DSparseRegisteredImage *pose =
|
|
&publication->registered_images[index];
|
|
if (!sparse_find_component(publication, pose->component_key)) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
break;
|
|
}
|
|
}
|
|
for (size_t index = 0;
|
|
result == LARDON3D_PROJECT_DB_OK && index < publication->landmark_count;
|
|
++index) {
|
|
const Lardon3DSparseLandmark *landmark = &publication->landmarks[index];
|
|
if (!sparse_find_component(publication, landmark->component_key)) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
break;
|
|
}
|
|
sqlite3_bind_int64(track_component_statement, 1,
|
|
(sqlite3_int64)publication->track_set_id);
|
|
sqlite3_bind_int64(track_component_statement, 2,
|
|
(sqlite3_int64)landmark->track_id);
|
|
while (sqlite3_step(track_component_statement) == SQLITE_ROW) {
|
|
uint64_t image_id = (uint64_t)sqlite3_column_int64(
|
|
track_component_statement, 0);
|
|
const Lardon3DSparseRegisteredImage *pose =
|
|
sparse_find_registered_image(publication, image_id);
|
|
if (!pose || pose->component_key != landmark->component_key) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
break;
|
|
}
|
|
}
|
|
sqlite3_reset(track_component_statement);
|
|
sqlite3_clear_bindings(track_component_statement);
|
|
}
|
|
for (size_t index = 0; result == LARDON3D_PROJECT_DB_OK &&
|
|
index < publication->registered_image_count;
|
|
++index) {
|
|
sqlite3_bind_int64(scope_statement, 1,
|
|
(sqlite3_int64)publication->calibration_scope_id);
|
|
sqlite3_bind_int64(
|
|
scope_statement, 2,
|
|
(sqlite3_int64)publication->registered_images[index].image_id);
|
|
int scope_code = sqlite3_step(scope_statement);
|
|
if (scope_code != SQLITE_ROW)
|
|
if (scope_code != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
sqlite3_reset(scope_statement);
|
|
sqlite3_clear_bindings(scope_statement);
|
|
}
|
|
for (size_t index = 0;
|
|
result == LARDON3D_PROJECT_DB_OK && index < publication->landmark_count;
|
|
++index) {
|
|
sqlite3_bind_int64(track_statement, 1,
|
|
(sqlite3_int64)publication->track_set_id);
|
|
sqlite3_bind_int64(track_statement, 2,
|
|
(sqlite3_int64)publication->landmarks[index].track_id);
|
|
int track_code = sqlite3_step(track_statement);
|
|
if (track_code != SQLITE_ROW)
|
|
if (track_code != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
sqlite3_reset(track_statement);
|
|
sqlite3_clear_bindings(track_statement);
|
|
}
|
|
for (size_t index = 0; result == LARDON3D_PROJECT_DB_OK &&
|
|
index < publication->observation_count;
|
|
++index) {
|
|
const Lardon3DSparseLandmarkObservation *ob =
|
|
&publication->observations[index];
|
|
if (!sparse_find_landmark(publication, ob->track_id)) {
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
break;
|
|
}
|
|
sqlite3_bind_int64(observation_statement, 1,
|
|
(sqlite3_int64)publication->track_set_id);
|
|
sqlite3_bind_int64(observation_statement, 2, (sqlite3_int64)ob->track_id);
|
|
sqlite3_bind_int64(observation_statement, 3,
|
|
(sqlite3_int64)ob->feature_set_id);
|
|
sqlite3_bind_int64(observation_statement, 4, ob->feature_index);
|
|
sqlite3_bind_int64(observation_statement, 5, ob->position_in_track);
|
|
int observation_code = sqlite3_step(observation_statement);
|
|
if (observation_code != SQLITE_ROW)
|
|
if (observation_code != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CONSTRAINT;
|
|
sqlite3_reset(observation_statement);
|
|
sqlite3_clear_bindings(observation_statement);
|
|
}
|
|
if (scope_statement)
|
|
sqlite3_finalize(scope_statement);
|
|
if (track_statement)
|
|
sqlite3_finalize(track_statement);
|
|
if (observation_statement)
|
|
sqlite3_finalize(observation_statement);
|
|
if (track_component_statement)
|
|
sqlite3_finalize(track_component_statement);
|
|
return result;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult
|
|
sparse_load_reconstruction_locked(Lardon3DProjectDb *db, uint64_t id,
|
|
Lardon3DSparseReconstruction *output) {
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT "
|
|
"track_set_id,calibration_scope_id,sfm_kind,sfm_version,"
|
|
"parameter_fingerprint,derivation_identity,component_count,registered_image_count,"
|
|
"landmark_count,reprojection_rmse_px,reprojection_median_px FROM "
|
|
"sparse_reconstructions WHERE reconstruction_id=?1",
|
|
&statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
return result;
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 4) != 32 ||
|
|
(sqlite3_column_type(statement, 5) != SQLITE_NULL &&
|
|
sqlite3_column_bytes(statement, 5) != 32))
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else {
|
|
memset(output, 0, sizeof(*output));
|
|
output->reconstruction_id = id;
|
|
output->track_set_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
output->calibration_scope_id = (uint64_t)sqlite3_column_int64(statement, 1);
|
|
output->sfm_kind = (uint32_t)sqlite3_column_int64(statement, 2);
|
|
output->sfm_version = (uint32_t)sqlite3_column_int64(statement, 3);
|
|
memcpy(output->parameter_fingerprint, sqlite3_column_blob(statement, 4),
|
|
32);
|
|
output->has_derivation_identity =
|
|
sqlite3_column_type(statement, 5) != SQLITE_NULL;
|
|
if (output->has_derivation_identity)
|
|
memcpy(output->derivation_identity, sqlite3_column_blob(statement, 5),
|
|
32);
|
|
output->component_count = (uint64_t)sqlite3_column_int64(statement, 6);
|
|
output->registered_image_count =
|
|
(uint64_t)sqlite3_column_int64(statement, 7);
|
|
output->landmark_count = (uint64_t)sqlite3_column_int64(statement, 8);
|
|
output->reprojection_rmse_px = sqlite3_column_double(statement, 9);
|
|
output->reprojection_median_px = sqlite3_column_double(statement, 10);
|
|
if (!output->track_set_id || !output->calibration_scope_id ||
|
|
output->sfm_kind != LARDON3D_SPARSE_SFM_KIND_INCREMENTAL ||
|
|
output->sfm_version != LARDON3D_SPARSE_SFM_VERSION ||
|
|
output->component_count == 0 || output->registered_image_count < 2 ||
|
|
output->landmark_count == 0 ||
|
|
!sparse_finite(output->reprojection_rmse_px) ||
|
|
!sparse_finite(output->reprojection_median_px))
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
memset(output, 0, sizeof(*output));
|
|
return result;
|
|
}
|
|
|
|
static Lardon3DProjectDbResult sparse_reconstruction_publish_internal(
|
|
Lardon3DProjectDb *db, const Lardon3DSparsePublication *publication,
|
|
const Lardon3DIncrementalReconstructionMetadata *metadata,
|
|
Lardon3DSparseReconstruction *output) {
|
|
if (!db || !output || !sparse_publication_valid(publication) ||
|
|
(metadata &&
|
|
memcmp(publication->parameter_fingerprint,
|
|
metadata->parameter_fingerprint, 32) != 0) ||
|
|
(metadata &&
|
|
(!sparse_fits_sqlite(metadata->base_reconstruction_id) ||
|
|
metadata->base_reconstruction_id == 0 ||
|
|
metadata->extension_track_set_id != publication->track_set_id ||
|
|
metadata->calibration_scope_id != publication->calibration_scope_id ||
|
|
metadata->incremental_kind != 1 || metadata->incremental_version != 1 ||
|
|
!sparse_incremental_identity_valid(metadata))))
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
execute(db, "BEGIN IMMEDIATE", "begin sparse reconstruction");
|
|
sqlite3_stmt *statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(db, "SELECT 1 FROM track_sets WHERE track_set_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1,
|
|
(sqlite3_int64)(sqlite3_int64)publication->track_set_id);
|
|
if (sqlite3_step(statement) != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result =
|
|
prepare(db, "SELECT 1 FROM sparse_calibration_scopes WHERE scope_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(
|
|
statement, 1,
|
|
(sqlite3_int64)(sqlite3_int64)publication->calibration_scope_id);
|
|
if (sqlite3_step(statement) != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = sparse_validate_relations(db, publication);
|
|
uint64_t reconstruction_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"INSERT INTO "
|
|
"sparse_reconstructions(track_set_id,calibration_scope_id,sfm_kind,sfm_"
|
|
"version,parameter_fingerprint,derivation_identity,component_count,registered_image_count,"
|
|
"landmark_count,reprojection_rmse_px,reprojection_median_px,created_at)"
|
|
" VALUES(?1,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12)",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1,
|
|
(sqlite3_int64)(sqlite3_int64)publication->track_set_id);
|
|
sqlite3_bind_int64(
|
|
statement, 2,
|
|
(sqlite3_int64)(sqlite3_int64)publication->calibration_scope_id);
|
|
sqlite3_bind_int64(statement, 3, publication->sfm_kind);
|
|
sqlite3_bind_int64(statement, 4, publication->sfm_version);
|
|
sqlite3_bind_blob(statement, 5, publication->parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
if (metadata)
|
|
sqlite3_bind_blob(statement, 6, metadata->scientific_identity, 32,
|
|
SQLITE_TRANSIENT);
|
|
else
|
|
sqlite3_bind_null(statement, 6);
|
|
sqlite3_bind_int64(statement, 7,
|
|
(sqlite3_int64)publication->component_count);
|
|
sqlite3_bind_int64(statement, 8,
|
|
(sqlite3_int64)publication->registered_image_count);
|
|
sqlite3_bind_int64(statement, 9,
|
|
(sqlite3_int64)publication->landmark_count);
|
|
sqlite3_bind_double(statement, 10, publication->reprojection_rmse_px);
|
|
sqlite3_bind_double(statement, 11, publication->reprojection_median_px);
|
|
sqlite3_bind_int64(statement, 12, publication->created_at);
|
|
result = step_done(db, statement, "insert sparse reconstruction");
|
|
statement = NULL;
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
reconstruction_id = (uint64_t)sqlite3_last_insert_rowid(db->connection);
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"INSERT INTO "
|
|
"sparse_reconstruction_components(reconstruction_id,component_key,"
|
|
"registered_image_count,landmark_count) VALUES(?1,?2,?3,?4)",
|
|
&statement);
|
|
for (size_t index = 0;
|
|
result == LARDON3D_PROJECT_DB_OK && index < publication->component_count;
|
|
++index) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(
|
|
statement, 2,
|
|
(sqlite3_int64)publication->components[index].component_key);
|
|
sqlite3_bind_int64(
|
|
statement, 3,
|
|
(sqlite3_int64)publication->components[index].registered_image_count);
|
|
sqlite3_bind_int64(
|
|
statement, 4,
|
|
(sqlite3_int64)publication->components[index].landmark_count);
|
|
result = sparse_step_reuse(db, statement, "insert sparse component");
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(db,
|
|
"INSERT INTO "
|
|
"sparse_registered_images(reconstruction_id,component_id,"
|
|
"image_id,rotation_00,rotation_01,rotation_02,rotation_10,"
|
|
"rotation_11,rotation_12,rotation_20,rotation_21,rotation_"
|
|
"22,translation_0,translation_1,translation_2) SELECT "
|
|
"?1,component_id,?2,?3,?4,?5,?6,?7,?8,?9,?10,?11,?12,?13,?"
|
|
"14 FROM sparse_reconstruction_components WHERE "
|
|
"reconstruction_id=?1 AND component_key=?15",
|
|
&statement);
|
|
for (size_t index = 0; result == LARDON3D_PROJECT_DB_OK &&
|
|
index < publication->registered_image_count;
|
|
++index) {
|
|
const Lardon3DSparseRegisteredImage *pose =
|
|
&publication->registered_images[index];
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)pose->image_id);
|
|
for (int bind = 0; bind < 9; ++bind)
|
|
sqlite3_bind_double(statement, 3 + bind, pose->rotation_cw[bind]);
|
|
for (int bind = 0; bind < 3; ++bind)
|
|
sqlite3_bind_double(statement, 12 + bind, pose->translation_cw[bind]);
|
|
sqlite3_bind_int64(statement, 15, (sqlite3_int64)pose->component_key);
|
|
result = sparse_step_reuse(db, statement, "insert sparse pose");
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result =
|
|
prepare(db,
|
|
"INSERT INTO "
|
|
"sparse_landmarks(reconstruction_id,component_id,track_id,x,y,"
|
|
"z,reprojection_rmse_px,reprojection_median_px,observation_"
|
|
"count) SELECT ?1,component_id,?2,?3,?4,?5,?6,?7,?8 FROM "
|
|
"sparse_reconstruction_components WHERE reconstruction_id=?1 "
|
|
"AND component_key=?9",
|
|
&statement);
|
|
for (size_t index = 0;
|
|
result == LARDON3D_PROJECT_DB_OK && index < publication->landmark_count;
|
|
++index) {
|
|
const Lardon3DSparseLandmark *landmark = &publication->landmarks[index];
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)landmark->track_id);
|
|
sqlite3_bind_double(statement, 3, landmark->x);
|
|
sqlite3_bind_double(statement, 4, landmark->y);
|
|
sqlite3_bind_double(statement, 5, landmark->z);
|
|
sqlite3_bind_double(statement, 6, landmark->reprojection_rmse_px);
|
|
sqlite3_bind_double(statement, 7, landmark->reprojection_median_px);
|
|
sqlite3_bind_int64(statement, 8,
|
|
(sqlite3_int64)landmark->observation_count);
|
|
sqlite3_bind_int64(statement, 9, (sqlite3_int64)landmark->component_key);
|
|
result = sparse_step_reuse(db, statement, "insert sparse landmark");
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"INSERT INTO "
|
|
"sparse_landmark_observations(landmark_id,feature_set_id,feature_index,"
|
|
"position_in_track) SELECT landmark_id,?2,?3,?4 FROM sparse_landmarks "
|
|
"WHERE reconstruction_id=?1 AND track_id=?5",
|
|
&statement);
|
|
for (size_t index = 0; result == LARDON3D_PROJECT_DB_OK &&
|
|
index < publication->observation_count;
|
|
++index) {
|
|
const Lardon3DSparseLandmarkObservation *ob =
|
|
&publication->observations[index];
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)ob->feature_set_id);
|
|
sqlite3_bind_int64(statement, 3, ob->feature_index);
|
|
sqlite3_bind_int64(statement, 4, ob->position_in_track);
|
|
sqlite3_bind_int64(statement, 5, (sqlite3_int64)ob->track_id);
|
|
result = sparse_step_reuse(db, statement, "insert sparse observation");
|
|
}
|
|
if (statement)
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
if (result == LARDON3D_PROJECT_DB_OK && metadata)
|
|
result = prepare(
|
|
db,
|
|
"INSERT INTO incremental_reconstructions(reconstruction_id,"
|
|
"base_reconstruction_id,extension_track_set_id,calibration_scope_id,"
|
|
"incremental_kind,incremental_version,parameter_fingerprint,"
|
|
"scientific_identity) VALUES(?1,?2,?3,?4,?5,?6,?7,?8)",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK && metadata) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2,
|
|
(sqlite3_int64)metadata->base_reconstruction_id);
|
|
sqlite3_bind_int64(statement, 3,
|
|
(sqlite3_int64)metadata->extension_track_set_id);
|
|
sqlite3_bind_int64(statement, 4,
|
|
(sqlite3_int64)metadata->calibration_scope_id);
|
|
sqlite3_bind_int(statement, 5, (int)metadata->incremental_kind);
|
|
sqlite3_bind_int(statement, 6, (int)metadata->incremental_version);
|
|
sqlite3_bind_blob(statement, 7, metadata->parameter_fingerprint, 32,
|
|
SQLITE_TRANSIENT);
|
|
sqlite3_bind_blob(statement, 8, metadata->scientific_identity, 32,
|
|
SQLITE_TRANSIENT);
|
|
result = step_done(db, statement, "insert incremental reconstruction metadata");
|
|
statement = NULL;
|
|
}
|
|
if (statement) sqlite3_finalize(statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = execute(db, "COMMIT", "commit sparse reconstruction");
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
(void)execute(db, "ROLLBACK", "rollback sparse reconstruction");
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = sparse_load_reconstruction_locked(db, reconstruction_id, output);
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_publish(
|
|
Lardon3DProjectDb *db, const Lardon3DSparsePublication *publication,
|
|
Lardon3DSparseReconstruction *output) {
|
|
return sparse_reconstruction_publish_internal(db, publication, NULL, output);
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_publish(
|
|
Lardon3DProjectDb *db, const Lardon3DSparsePublication *publication,
|
|
const Lardon3DIncrementalReconstructionMetadata *metadata,
|
|
Lardon3DSparseReconstruction *output) {
|
|
if (!metadata) return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
return sparse_reconstruction_publish_internal(db, publication, metadata, output);
|
|
}
|
|
|
|
static Lardon3DProjectDbResult incremental_metadata_locked(
|
|
Lardon3DProjectDb *db, uint64_t reconstruction_id,
|
|
Lardon3DIncrementalReconstructionMetadata *metadata) {
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT base_reconstruction_id,extension_track_set_id,calibration_scope_id,"
|
|
"incremental_kind,incremental_version,parameter_fingerprint,scientific_identity "
|
|
"FROM incremental_reconstructions WHERE reconstruction_id=?1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW || sqlite3_column_bytes(statement, 5) != 32 ||
|
|
sqlite3_column_bytes(statement, 6) != 32)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else {
|
|
memset(metadata, 0, sizeof(*metadata));
|
|
metadata->reconstruction_id = reconstruction_id;
|
|
metadata->base_reconstruction_id =
|
|
(uint64_t)sqlite3_column_int64(statement, 0);
|
|
metadata->extension_track_set_id =
|
|
(uint64_t)sqlite3_column_int64(statement, 1);
|
|
metadata->calibration_scope_id =
|
|
(uint64_t)sqlite3_column_int64(statement, 2);
|
|
metadata->incremental_kind = (uint32_t)sqlite3_column_int(statement, 3);
|
|
metadata->incremental_version = (uint32_t)sqlite3_column_int(statement, 4);
|
|
memcpy(metadata->parameter_fingerprint, sqlite3_column_blob(statement, 5), 32);
|
|
memcpy(metadata->scientific_identity, sqlite3_column_blob(statement, 6), 32);
|
|
if (!metadata->base_reconstruction_id || !metadata->extension_track_set_id ||
|
|
!metadata->calibration_scope_id || metadata->incremental_kind != 1 ||
|
|
metadata->incremental_version != 1 ||
|
|
!sparse_incremental_identity_valid(metadata))
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) memset(metadata, 0, sizeof(*metadata));
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_load_metadata(
|
|
Lardon3DProjectDb *db, uint64_t reconstruction_id,
|
|
Lardon3DIncrementalReconstructionMetadata *metadata) {
|
|
if (!db || !reconstruction_id || !metadata)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
incremental_metadata_locked(db, reconstruction_id, metadata);
|
|
Lardon3DSparseReconstruction reconstruction = {0};
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = sparse_load_reconstruction_locked(db, reconstruction_id,
|
|
&reconstruction);
|
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
|
(!reconstruction.has_derivation_identity ||
|
|
memcmp(reconstruction.derivation_identity,
|
|
metadata->scientific_identity, 32) != 0 ||
|
|
memcmp(reconstruction.parameter_fingerprint,
|
|
metadata->parameter_fingerprint, 32) != 0))
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_incremental_reconstruction_find_exact(
|
|
Lardon3DProjectDb *db, const unsigned char scientific_identity[32],
|
|
Lardon3DIncrementalReconstructionMetadata *metadata,
|
|
Lardon3DSparseReconstruction *reconstruction) {
|
|
if (!db || !scientific_identity || !metadata || !reconstruction)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
memset(metadata, 0, sizeof(*metadata));
|
|
memset(reconstruction, 0, sizeof(*reconstruction));
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT reconstruction_id FROM incremental_reconstructions WHERE "
|
|
"scientific_identity=?1",
|
|
&statement);
|
|
uint64_t reconstruction_id = 0;
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_blob(statement, 1, scientific_identity, 32, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else
|
|
reconstruction_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = incremental_metadata_locked(db, reconstruction_id, metadata);
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = sparse_load_reconstruction_locked(db, reconstruction_id, reconstruction);
|
|
if (result == LARDON3D_PROJECT_DB_OK &&
|
|
(!reconstruction->has_derivation_identity ||
|
|
memcmp(reconstruction->derivation_identity, scientific_identity, 32) != 0 ||
|
|
memcmp(reconstruction->parameter_fingerprint,
|
|
metadata->parameter_fingerprint, 32) != 0))
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
memset(metadata, 0, sizeof(*metadata));
|
|
memset(reconstruction, 0, sizeof(*reconstruction));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_sparse_reconstruction_find_exact(
|
|
Lardon3DProjectDb *db, uint64_t track_set_id, uint64_t scope_id,
|
|
uint32_t kind, uint32_t version, const unsigned char fingerprint[32],
|
|
Lardon3DSparseReconstruction *output) {
|
|
if (!db || !track_set_id || !scope_id || !fingerprint || !output)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT reconstruction_id FROM sparse_reconstructions WHERE "
|
|
"track_set_id=?1 AND calibration_scope_id=?2 AND sfm_kind=?3 AND "
|
|
"sfm_version=?4 AND parameter_fingerprint=?5 AND "
|
|
"derivation_identity IS NULL",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)track_set_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)scope_id);
|
|
sqlite3_bind_int64(statement, 3, kind);
|
|
sqlite3_bind_int64(statement, 4, version);
|
|
sqlite3_bind_blob(statement, 5, fingerprint, 32, SQLITE_TRANSIENT);
|
|
int code = sqlite3_step(statement);
|
|
if (code == SQLITE_DONE)
|
|
result = LARDON3D_PROJECT_DB_NOT_FOUND;
|
|
else if (code != SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
else
|
|
result = sparse_load_reconstruction_locked(
|
|
db, (uint64_t)sqlite3_column_int64(statement, 0), output);
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_reconstruction_load(Lardon3DProjectDb *db, uint64_t id,
|
|
Lardon3DSparseReconstruction *output) {
|
|
if (!db || !id || !output)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
Lardon3DProjectDbResult result =
|
|
sparse_load_reconstruction_locked(db, id, output);
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_reconstruction_list(Lardon3DProjectDb *db, uint64_t after_id,
|
|
size_t capacity,
|
|
Lardon3DSparseReconstructionPage *page) {
|
|
if (!db || !page || !page->items || capacity > 64)
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
page->after_id = after_id;
|
|
page->capacity = capacity;
|
|
page->count = 0;
|
|
page->next_after_id = after_id;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT reconstruction_id FROM sparse_reconstructions WHERE "
|
|
"reconstruction_id>?1 ORDER BY reconstruction_id LIMIT ?2",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)after_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)capacity);
|
|
while (page->count < capacity && sqlite3_step(statement) == SQLITE_ROW) {
|
|
uint64_t id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
result =
|
|
sparse_load_reconstruction_locked(db, id, &page->items[page->count]);
|
|
if (result != LARDON3D_PROJECT_DB_OK)
|
|
break;
|
|
page->next_after_id = id;
|
|
++page->count;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
static bool sparse_page_capacity_valid(size_t capacity) {
|
|
return capacity <= LARDON3D_SPARSE_SFM_PAGE_MAX;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_component_list(Lardon3DProjectDb *db,
|
|
uint64_t reconstruction_id,
|
|
uint64_t after_component_key, size_t capacity,
|
|
Lardon3DSparseComponentPage *page) {
|
|
if (!db || !page || !page->items || !reconstruction_id ||
|
|
!sparse_page_capacity_valid(capacity))
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
page->after_component_key = after_component_key;
|
|
page->capacity = capacity;
|
|
page->count = 0;
|
|
page->next_component_key = after_component_key;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result =
|
|
prepare(db,
|
|
"SELECT 1 FROM sparse_reconstruction_components AS c WHERE "
|
|
"c.reconstruction_id=?1 AND (c.component_key!=(SELECT "
|
|
"MIN(r.image_id) FROM sparse_registered_images AS r WHERE "
|
|
"r.reconstruction_id=c.reconstruction_id AND "
|
|
"r.component_id=c.component_id) OR c.registered_image_count!=("
|
|
"SELECT COUNT(*) FROM sparse_registered_images AS r2 WHERE "
|
|
"r2.reconstruction_id=c.reconstruction_id AND "
|
|
"r2.component_id=c.component_id) OR c.landmark_count!=(SELECT "
|
|
"COUNT(*) FROM sparse_landmarks AS l WHERE "
|
|
"l.reconstruction_id=c.reconstruction_id AND "
|
|
"l.component_id=c.component_id)) LIMIT 1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
if (sqlite3_step(statement) == SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(db,
|
|
"SELECT component_id,component_key,registered_image_count,"
|
|
"landmark_count FROM sparse_reconstruction_components "
|
|
"WHERE reconstruction_id=?1 AND component_key>?2 "
|
|
"ORDER BY component_key LIMIT ?3",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_component_key);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
|
while (page->count < capacity && sqlite3_step(statement) == SQLITE_ROW) {
|
|
Lardon3DSparseComponent *item = &page->items[page->count];
|
|
item->component_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
item->component_key = (uint64_t)sqlite3_column_int64(statement, 1);
|
|
item->registered_image_count =
|
|
(uint64_t)sqlite3_column_int64(statement, 2);
|
|
item->landmark_count = (uint64_t)sqlite3_column_int64(statement, 3);
|
|
page->next_component_key = item->component_key;
|
|
++page->count;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
page->count = 0;
|
|
page->next_component_key = after_component_key;
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_sparse_registered_image_list(
|
|
Lardon3DProjectDb *db, uint64_t reconstruction_id, uint64_t after_image_id,
|
|
size_t capacity, Lardon3DSparseRegisteredImagePage *page) {
|
|
if (!db || !page || !page->items || !reconstruction_id ||
|
|
!sparse_page_capacity_valid(capacity))
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
page->after_image_id = after_image_id;
|
|
page->capacity = capacity;
|
|
page->count = 0;
|
|
page->next_image_id = after_image_id;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT 1 FROM sparse_reconstruction_components AS c WHERE "
|
|
"c.reconstruction_id=?1 AND c.component_key!=(SELECT MIN(r.image_id) "
|
|
"FROM sparse_registered_images AS r WHERE r.reconstruction_id=?1 AND "
|
|
"r.component_id=c.component_id) LIMIT 1",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
if (sqlite3_step(statement) == SQLITE_ROW)
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
sqlite3_finalize(statement);
|
|
statement = NULL;
|
|
}
|
|
if (result == LARDON3D_PROJECT_DB_OK)
|
|
result = prepare(
|
|
db,
|
|
"SELECT "
|
|
"r.image_id,r.component_id,r.rotation_00,r.rotation_01,r.rotation_02,"
|
|
"r.rotation_10,r.rotation_11,r.rotation_12,r.rotation_20,r.rotation_21,"
|
|
"r.rotation_22,"
|
|
"translation_0,translation_1,translation_2,c.component_key "
|
|
"FROM sparse_registered_images AS r JOIN "
|
|
"sparse_reconstruction_components AS c ON c.reconstruction_id="
|
|
"r.reconstruction_id AND c.component_id=r.component_id "
|
|
"WHERE r.reconstruction_id=?1 AND r.image_id>?2 ORDER BY r.image_id "
|
|
"LIMIT ?3",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_image_id);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
|
while (page->count < capacity && sqlite3_step(statement) == SQLITE_ROW) {
|
|
Lardon3DSparseRegisteredImage *item = &page->items[page->count];
|
|
item->image_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
item->component_key = 0;
|
|
for (int index = 0; index < 9; ++index)
|
|
item->rotation_cw[index] = sqlite3_column_double(statement, index + 2);
|
|
for (int index = 0; index < 3; ++index)
|
|
item->translation_cw[index] =
|
|
sqlite3_column_double(statement, index + 11);
|
|
item->component_key = (uint64_t)sqlite3_column_int64(statement, 14);
|
|
if (!sparse_rotation_valid(item->rotation_cw) ||
|
|
!sparse_finite(item->translation_cw[0]) ||
|
|
!sparse_finite(item->translation_cw[1]) ||
|
|
!sparse_finite(item->translation_cw[2])) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
page->next_image_id = item->image_id;
|
|
++page->count;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
page->count = 0;
|
|
page->next_image_id = after_image_id;
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult
|
|
lardon3d_sparse_landmark_list(Lardon3DProjectDb *db, uint64_t reconstruction_id,
|
|
uint64_t after_track_id, size_t capacity,
|
|
Lardon3DSparseLandmarkPage *page) {
|
|
if (!db || !page || !page->items || !reconstruction_id ||
|
|
!sparse_page_capacity_valid(capacity))
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
page->after_track_id = after_track_id;
|
|
page->capacity = capacity;
|
|
page->count = 0;
|
|
page->next_track_id = after_track_id;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT l.landmark_id,l.track_id,l.component_id,l.x,l.y,l.z,"
|
|
"l.reprojection_rmse_px,l.reprojection_median_px,l.observation_count,"
|
|
"c.component_key,CASE WHEN c.component_key=(SELECT MIN(ri.image_id) "
|
|
"FROM sparse_registered_images AS ri WHERE ri.reconstruction_id="
|
|
"l.reconstruction_id AND ri.component_id=l.component_id) AND "
|
|
"EXISTS (SELECT 1 FROM tracks AS tr WHERE tr.track_set_id=r.track_set_id "
|
|
"AND tr.track_id=l.track_id) AND NOT EXISTS (SELECT 1 FROM "
|
|
"track_observations AS t JOIN feature_sets AS f ON "
|
|
"f.feature_set_id=t.feature_set_id WHERE t.track_set_id=r.track_set_id "
|
|
"AND t.track_id=l.track_id AND NOT EXISTS (SELECT 1 FROM "
|
|
"sparse_registered_images AS ri2 WHERE ri2.reconstruction_id="
|
|
"l.reconstruction_id AND ri2.image_id=f.image_id AND "
|
|
"ri2.component_id=l.component_id)) THEN 1 ELSE 0 END "
|
|
"FROM sparse_landmarks AS l JOIN sparse_reconstructions AS r ON "
|
|
"r.reconstruction_id=l.reconstruction_id JOIN "
|
|
"sparse_reconstruction_components AS c ON "
|
|
"c.reconstruction_id=l.reconstruction_id AND c.component_id=l.component_id "
|
|
"WHERE l.reconstruction_id=?1 AND l.track_id>?2 ORDER BY l.track_id "
|
|
"LIMIT ?3",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_track_id);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)capacity);
|
|
while (page->count < capacity && sqlite3_step(statement) == SQLITE_ROW) {
|
|
Lardon3DSparseLandmark *item = &page->items[page->count];
|
|
item->landmark_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
item->track_id = (uint64_t)sqlite3_column_int64(statement, 1);
|
|
item->component_key = (uint64_t)sqlite3_column_int64(statement, 9);
|
|
item->x = sqlite3_column_double(statement, 3);
|
|
item->y = sqlite3_column_double(statement, 4);
|
|
item->z = sqlite3_column_double(statement, 5);
|
|
item->reprojection_rmse_px = sqlite3_column_double(statement, 6);
|
|
item->reprojection_median_px = sqlite3_column_double(statement, 7);
|
|
item->observation_count = (uint64_t)sqlite3_column_int64(statement, 8);
|
|
if (sqlite3_column_int(statement, 10) == 0) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
if (!sparse_finite(item->x) || !sparse_finite(item->y) ||
|
|
!sparse_finite(item->z) ||
|
|
!sparse_finite(item->reprojection_rmse_px) ||
|
|
!sparse_finite(item->reprojection_median_px)) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
page->next_track_id = item->track_id;
|
|
++page->count;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
if (result != LARDON3D_PROJECT_DB_OK) {
|
|
page->count = 0;
|
|
page->next_track_id = after_track_id;
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|
|
|
|
Lardon3DProjectDbResult lardon3d_sparse_observation_list(
|
|
Lardon3DProjectDb *db, uint64_t reconstruction_id,
|
|
uint64_t after_landmark_id, uint32_t after_position_in_track,
|
|
size_t capacity, Lardon3DSparseObservationPage *page) {
|
|
if (!db || !page || !page->items || !reconstruction_id ||
|
|
!sparse_page_capacity_valid(capacity))
|
|
return LARDON3D_PROJECT_DB_INVALID_ARGUMENT;
|
|
page->after_landmark_id = after_landmark_id;
|
|
page->after_position_in_track = after_position_in_track;
|
|
page->capacity = capacity;
|
|
page->count = 0;
|
|
page->next_landmark_id = after_landmark_id;
|
|
page->next_position_in_track = after_position_in_track;
|
|
(void)pthread_mutex_lock(&db->mutex);
|
|
sqlite3_stmt *statement = NULL;
|
|
Lardon3DProjectDbResult result = prepare(
|
|
db,
|
|
"SELECT o.landmark_id,l.track_id,o.feature_set_id,o.feature_index,"
|
|
"o.position_in_track,CASE WHEN EXISTS (SELECT 1 FROM "
|
|
"track_observations AS t WHERE t.track_set_id=r.track_set_id AND "
|
|
"t.track_id=l.track_id AND t.feature_set_id=o.feature_set_id AND "
|
|
"t.feature_index=o.feature_index AND "
|
|
"t.position_in_track=o.position_in_track) THEN 1 ELSE 0 END "
|
|
"FROM sparse_landmark_observations AS o JOIN sparse_landmarks AS l ON "
|
|
"l.landmark_id=o.landmark_id JOIN sparse_reconstructions AS r ON "
|
|
"r.reconstruction_id=l.reconstruction_id WHERE l.reconstruction_id=?1 "
|
|
"AND (o.landmark_id>?2 OR (o.landmark_id=?2 AND "
|
|
"o.position_in_track>?3)) ORDER BY o.landmark_id,o.position_in_track "
|
|
"LIMIT ?4",
|
|
&statement);
|
|
if (result == LARDON3D_PROJECT_DB_OK) {
|
|
sqlite3_bind_int64(statement, 1, (sqlite3_int64)reconstruction_id);
|
|
sqlite3_bind_int64(statement, 2, (sqlite3_int64)after_landmark_id);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)after_position_in_track);
|
|
sqlite3_bind_int64(statement, 4, (sqlite3_int64)capacity);
|
|
while (page->count < capacity && sqlite3_step(statement) == SQLITE_ROW) {
|
|
Lardon3DSparseLandmarkObservation *item = &page->items[page->count];
|
|
item->landmark_id = (uint64_t)sqlite3_column_int64(statement, 0);
|
|
item->track_id = (uint64_t)sqlite3_column_int64(statement, 1);
|
|
item->feature_set_id = (uint64_t)sqlite3_column_int64(statement, 2);
|
|
item->feature_index = (uint32_t)sqlite3_column_int64(statement, 3);
|
|
item->position_in_track = (uint32_t)sqlite3_column_int64(statement, 4);
|
|
if (sqlite3_column_int(statement, 5) == 0) {
|
|
result = LARDON3D_PROJECT_DB_CORRUPT;
|
|
break;
|
|
}
|
|
page->next_landmark_id = item->landmark_id;
|
|
page->next_position_in_track = item->position_in_track;
|
|
++page->count;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
}
|
|
(void)pthread_mutex_unlock(&db->mutex);
|
|
return result;
|
|
}
|