813 lines
39 KiB
C
813 lines
39 KiB
C
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <pthread.h>
|
|
#include <openssl/evp.h>
|
|
#include <sqlite3.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include <lardon3d/feature_store.h>
|
|
#include <lardon3d/image_catalog.h>
|
|
#include <lardon3d/visual_index.h>
|
|
|
|
#define CHECK(condition) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
typedef struct {
|
|
const char *root;
|
|
Lardon3DProjectDb *database;
|
|
uint64_t index_id;
|
|
uint64_t query_id;
|
|
bool ok;
|
|
} QueryThread;
|
|
|
|
typedef struct {
|
|
pthread_barrier_t barrier;
|
|
const char *root;
|
|
Lardon3DProjectDb *database;
|
|
uint64_t index_id;
|
|
Lardon3DVisualIndexResult result[2];
|
|
} UpdateRace;
|
|
|
|
typedef struct {
|
|
UpdateRace *race;
|
|
size_t slot;
|
|
} UpdateRaceThread;
|
|
|
|
static size_t candidate_rank(const Lardon3DVisualIndexCandidate *candidates, size_t count,
|
|
uint64_t feature_set_id) {
|
|
for (size_t i = 0; i < count; ++i) {
|
|
if (candidates[i].feature_set_id == feature_set_id) {
|
|
return i;
|
|
}
|
|
}
|
|
return SIZE_MAX;
|
|
}
|
|
|
|
static bool join_path(char output[PATH_MAX], const char *left, const char *right) {
|
|
int written = snprintf(output, PATH_MAX, "%s/%s", left, right);
|
|
return written > 0 && (size_t)written < PATH_MAX;
|
|
}
|
|
|
|
static bool remove_tree(const char *path) {
|
|
struct stat status;
|
|
if (lstat(path, &status) != 0) {
|
|
return errno == ENOENT;
|
|
}
|
|
if (!S_ISDIR(status.st_mode)) {
|
|
return unlink(path) == 0;
|
|
}
|
|
DIR *directory = opendir(path);
|
|
if (!directory) {
|
|
return false;
|
|
}
|
|
bool ok = true;
|
|
for (struct dirent *entry = readdir(directory); entry; entry = readdir(directory)) {
|
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
char child[PATH_MAX];
|
|
if (!join_path(child, path, entry->d_name) || !remove_tree(child)) {
|
|
ok = false;
|
|
}
|
|
}
|
|
return closedir(directory) == 0 && rmdir(path) == 0 && ok;
|
|
}
|
|
|
|
static bool write_source(const char *path, unsigned char value) {
|
|
int descriptor = open(path, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
|
|
if (descriptor < 0) {
|
|
return false;
|
|
}
|
|
unsigned char bytes[64];
|
|
memset(bytes, value, sizeof(bytes));
|
|
bool ok = write(descriptor, bytes, sizeof(bytes)) == (ssize_t)sizeof(bytes);
|
|
return close(descriptor) == 0 && ok;
|
|
}
|
|
|
|
static unsigned char scene_pixel(unsigned int x, unsigned int y, unsigned int seed) {
|
|
uint32_t value = x * 2654435761U ^ y * 2246822519U ^ seed * 3266489917U;
|
|
value ^= value >> 13;
|
|
value *= 1274126177U;
|
|
return (unsigned char)(value >> 24);
|
|
}
|
|
|
|
static bool write_scene(const char *path, unsigned int shift, unsigned int seed) {
|
|
FILE *file = fopen(path, "wb");
|
|
if (!file || fprintf(file, "P5\n256 256\n255\n") <= 0) {
|
|
if (file) {
|
|
fclose(file);
|
|
}
|
|
return false;
|
|
}
|
|
for (unsigned int y = 0; y < 256; ++y) {
|
|
for (unsigned int x = 0; x < 256; ++x) {
|
|
unsigned char value = x >= shift ? scene_pixel(x - shift, y, seed) : 0;
|
|
if (fwrite(&value, 1, 1, file) != 1) {
|
|
fclose(file);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return fclose(file) == 0;
|
|
}
|
|
|
|
static bool write_transformed_scene(const char *path, unsigned int seed, bool crop,
|
|
double angle_degrees) {
|
|
unsigned int width = crop ? 192U : 256U;
|
|
FILE *file = fopen(path, "wb");
|
|
if (!file || fprintf(file, "P5\n%u %u\n255\n", width, width) <= 0) {
|
|
if (file) {
|
|
fclose(file);
|
|
}
|
|
return false;
|
|
}
|
|
double angle = angle_degrees * 3.14159265358979323846 / 180.0;
|
|
double cosine = cos(angle);
|
|
double sine = sin(angle);
|
|
for (unsigned int y = 0; y < width; ++y) {
|
|
for (unsigned int x = 0; x < width; ++x) {
|
|
double output_x = crop ? (double)x + 32.0 : (double)x;
|
|
double output_y = crop ? (double)y + 32.0 : (double)y;
|
|
double dx = output_x - 127.5;
|
|
double dy = output_y - 127.5;
|
|
int source_x = (int)(127.5 + cosine * dx + sine * dy + 0.5);
|
|
int source_y = (int)(127.5 - sine * dx + cosine * dy + 0.5);
|
|
unsigned char value = source_x >= 0 && source_x < 256 && source_y >= 0 && source_y < 256
|
|
? scene_pixel((unsigned int)source_x, (unsigned int)source_y, seed)
|
|
: 0;
|
|
if (fwrite(&value, 1, 1, file) != 1) {
|
|
fclose(file);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return fclose(file) == 0;
|
|
}
|
|
|
|
static bool publish_real_features(Lardon3DAppState *state, uint64_t image_id,
|
|
const char *source, Lardon3DProjectDbFeatureSet *set) {
|
|
Lardon3DFeatureExtractorParameters parameters = {512, 4, 20};
|
|
Lardon3DExtractedFeatures features;
|
|
if (lardon3d_feature_extract_orb(source, ¶meters, &features) !=
|
|
LARDON3D_FEATURE_EXTRACT_OK) {
|
|
return false;
|
|
}
|
|
Lardon3DFeatureStoreResult result =
|
|
lardon3d_feature_store_publish(state, image_id, 0, ¶meters, &features, set);
|
|
lardon3d_extracted_features_destroy(&features);
|
|
return result == LARDON3D_FEATURE_STORE_OK ||
|
|
result == LARDON3D_FEATURE_STORE_PUBLISHED_NOT_DURABLE;
|
|
}
|
|
|
|
static bool publish_product_features(Lardon3DAppState *state, uint64_t image_id,
|
|
const char *source, Lardon3DProjectDbFeatureSet *set) {
|
|
Lardon3DFeatureExtractorParameters parameters = {640, 4, 20};
|
|
Lardon3DExtractedFeatures features;
|
|
if (lardon3d_feature_extract_orb(source, ¶meters, &features) !=
|
|
LARDON3D_FEATURE_EXTRACT_OK) {
|
|
return false;
|
|
}
|
|
Lardon3DFeatureStoreResult result =
|
|
lardon3d_feature_store_publish(state, image_id, 0, ¶meters, &features, set);
|
|
lardon3d_extracted_features_destroy(&features);
|
|
return result == LARDON3D_FEATURE_STORE_OK ||
|
|
result == LARDON3D_FEATURE_STORE_PUBLISHED_NOT_DURABLE;
|
|
}
|
|
|
|
static bool publish_features(Lardon3DAppState *state, uint64_t image_id, unsigned int family,
|
|
Lardon3DProjectDbFeatureSet *set) {
|
|
enum { COUNT = 16 };
|
|
Lardon3DFeatureKeypoint keypoints[COUNT];
|
|
unsigned char descriptors[COUNT * 32];
|
|
for (uint32_t i = 0; i < COUNT; ++i) {
|
|
keypoints[i] = (Lardon3DFeatureKeypoint){(float)i, (float)i, 8.0F, 0.0F,
|
|
(float)(COUNT - i), 0};
|
|
for (uint32_t byte = 0; byte < 32; ++byte) {
|
|
descriptors[i * 32 + byte] =
|
|
family == 0 || (family == 1 && i < 10)
|
|
? (unsigned char)(i * 17U + byte * 29U)
|
|
: (unsigned char)(family * 73U + i * 31U + byte * 7U);
|
|
}
|
|
}
|
|
Lardon3DExtractedFeatures features = {
|
|
.image_width = 64,
|
|
.image_height = 64,
|
|
.feature_count = COUNT,
|
|
.keypoints = keypoints,
|
|
.descriptors = descriptors,
|
|
.descriptor_bytes = sizeof(descriptors),
|
|
};
|
|
Lardon3DFeatureExtractorParameters parameters = {64, 4, 20};
|
|
Lardon3DFeatureStoreResult result =
|
|
lardon3d_feature_store_publish(state, image_id, 0, ¶meters, &features, set);
|
|
return result == LARDON3D_FEATURE_STORE_OK ||
|
|
result == LARDON3D_FEATURE_STORE_PUBLISHED_NOT_DURABLE;
|
|
}
|
|
|
|
static bool publish_empty_features(Lardon3DAppState *state, uint64_t image_id,
|
|
Lardon3DProjectDbFeatureSet *set) {
|
|
Lardon3DExtractedFeatures features = {.image_width = 64, .image_height = 64};
|
|
Lardon3DFeatureExtractorParameters parameters = {64, 4, 20};
|
|
Lardon3DFeatureStoreResult result =
|
|
lardon3d_feature_store_publish(state, image_id, 0, ¶meters, &features, set);
|
|
return result == LARDON3D_FEATURE_STORE_OK ||
|
|
result == LARDON3D_FEATURE_STORE_PUBLISHED_NOT_DURABLE;
|
|
}
|
|
|
|
static bool publish_burst_features(Lardon3DAppState *state, uint64_t image_id, unsigned int kind,
|
|
Lardon3DProjectDbFeatureSet *set) {
|
|
enum { COUNT = 16 };
|
|
Lardon3DFeatureKeypoint keypoints[COUNT];
|
|
unsigned char descriptors[COUNT * 32];
|
|
memset(descriptors, 0, sizeof(descriptors));
|
|
for (uint32_t i = 0; i < COUNT; ++i) {
|
|
keypoints[i] = (Lardon3DFeatureKeypoint){(float)i, (float)i, 8.0F, 0.0F,
|
|
(float)(COUNT - i), 0};
|
|
if ((kind == 0 && i >= 8) || (kind == 2 && i < 8)) {
|
|
uint32_t detail = kind == 0 ? i - 8 : i;
|
|
for (uint32_t byte = 0; byte < 32; ++byte) {
|
|
descriptors[i * 32 + byte] = (unsigned char)(detail * 37U + byte * 19U + 1U);
|
|
}
|
|
} else if (kind == 2 && i >= 8) {
|
|
memset(descriptors + i * 32, (int)(i + 31U), 32);
|
|
}
|
|
}
|
|
Lardon3DExtractedFeatures features = {
|
|
64, 64, COUNT, keypoints, descriptors, {0}, sizeof(descriptors)};
|
|
Lardon3DFeatureExtractorParameters parameters = {63, 4, 20};
|
|
Lardon3DFeatureStoreResult result =
|
|
lardon3d_feature_store_publish(state, image_id, 0, ¶meters, &features, set);
|
|
return result == LARDON3D_FEATURE_STORE_OK ||
|
|
result == LARDON3D_FEATURE_STORE_PUBLISHED_NOT_DURABLE;
|
|
}
|
|
|
|
static void update_race_barrier(void *userdata) {
|
|
UpdateRace *race = userdata;
|
|
(void)pthread_barrier_wait(&race->barrier);
|
|
}
|
|
|
|
static void *update_race_thread(void *userdata) {
|
|
UpdateRaceThread *thread = userdata;
|
|
uint64_t last = 0;
|
|
size_t indexed = 0;
|
|
thread->race->result[thread->slot] = lardon3d_visual_index_test_update_once(
|
|
thread->race->root, thread->race->database, thread->race->index_id, 0, 0, 16, &last,
|
|
&indexed, update_race_barrier, thread->race);
|
|
return NULL;
|
|
}
|
|
|
|
static bool sha256_bytes(const unsigned char *bytes, size_t size, unsigned char hash[32]) {
|
|
EVP_MD_CTX *context = EVP_MD_CTX_new();
|
|
unsigned int hash_size = 0;
|
|
bool ok = context && EVP_DigestInit_ex(context, EVP_sha256(), NULL) == 1 &&
|
|
EVP_DigestUpdate(context, bytes, size) == 1 &&
|
|
EVP_DigestFinal_ex(context, hash, &hash_size) == 1 && hash_size == 32;
|
|
EVP_MD_CTX_free(context);
|
|
return ok;
|
|
}
|
|
|
|
static bool replace_segment_metadata(const char *database_path, uint64_t segment_id,
|
|
const unsigned char hash[32], const char *path,
|
|
uint64_t size) {
|
|
sqlite3 *connection = NULL;
|
|
sqlite3_stmt *statement = NULL;
|
|
bool ok = sqlite3_open(database_path, &connection) == SQLITE_OK &&
|
|
sqlite3_prepare_v2(connection,
|
|
"UPDATE visual_index_segments SET sha256=?1,path=?2,size_bytes=?3 "
|
|
"WHERE visual_index_segment_id=?4", -1, &statement, NULL) ==
|
|
SQLITE_OK;
|
|
if (ok) {
|
|
sqlite3_bind_blob(statement, 1, hash, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_text(statement, 2, path, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(statement, 3, (sqlite3_int64)size);
|
|
sqlite3_bind_int64(statement, 4, (sqlite3_int64)segment_id);
|
|
ok = sqlite3_step(statement) == SQLITE_DONE && sqlite3_changes(connection) == 1;
|
|
}
|
|
sqlite3_finalize(statement);
|
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
|
}
|
|
|
|
static bool publish_mutated_segment(const char *root, const char *database_path,
|
|
const Lardon3DProjectDbVisualIndexSegment *segment,
|
|
const unsigned char *bytes, size_t size, char path[PATH_MAX],
|
|
unsigned char hash[32]) {
|
|
char hex[65];
|
|
if (!sha256_bytes(bytes, size, hash)) {
|
|
return false;
|
|
}
|
|
for (size_t i = 0; i < 32; ++i) {
|
|
snprintf(hex + i * 2, 3, "%02x", hash[i]);
|
|
}
|
|
char directory[PATH_MAX];
|
|
if (snprintf(path, PATH_MAX, "assets/visual-index/%.2s/%s", hex, hex) <= 0 ||
|
|
snprintf(directory, sizeof(directory), "%s/assets/visual-index/%.2s", root, hex) <= 0) {
|
|
return false;
|
|
}
|
|
if (mkdir(directory, 0700) != 0 && errno != EEXIST) {
|
|
return false;
|
|
}
|
|
char absolute[PATH_MAX];
|
|
if (!join_path(absolute, root, path)) {
|
|
return false;
|
|
}
|
|
int descriptor = open(absolute, O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0600);
|
|
if (descriptor < 0) {
|
|
return false;
|
|
}
|
|
bool ok = write(descriptor, bytes, size) == (ssize_t)size && close(descriptor) == 0;
|
|
return ok && replace_segment_metadata(database_path, segment->visual_index_segment_id, hash,
|
|
path, size);
|
|
}
|
|
|
|
static void *query_thread(void *userdata) {
|
|
QueryThread *context = userdata;
|
|
Lardon3DVisualIndexQueryOptions options = {
|
|
.top_k = 4,
|
|
.minimum_evidence_count = 1,
|
|
.scanset_filter = LARDON3D_VISUAL_INDEX_ANY_SCANSET,
|
|
};
|
|
Lardon3DVisualIndexCandidate candidates[LARDON3D_VISUAL_INDEX_TOP_K_MAX];
|
|
size_t count = 0;
|
|
context->ok = lardon3d_visual_index_query(context->root, context->database, context->index_id,
|
|
context->query_id, &options, candidates, 4, &count) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
count > 0;
|
|
return NULL;
|
|
}
|
|
|
|
static bool insert_scale_fixture(const char *database_path, uint64_t image_asset_id,
|
|
uint64_t feature_asset_id,
|
|
const Lardon3DProjectDbFeatureSet *prototype, uint64_t first,
|
|
uint64_t count) {
|
|
sqlite3 *connection = NULL;
|
|
if (sqlite3_open(database_path, &connection) != SQLITE_OK ||
|
|
sqlite3_exec(connection, "PRAGMA foreign_keys=ON;BEGIN IMMEDIATE", NULL, NULL, NULL) !=
|
|
SQLITE_OK) {
|
|
sqlite3_close(connection);
|
|
return false;
|
|
}
|
|
sqlite3_stmt *scan = NULL;
|
|
sqlite3_stmt *image = NULL;
|
|
sqlite3_stmt *feature = NULL;
|
|
bool ok = sqlite3_prepare_v2(connection,
|
|
"INSERT INTO scansets(scanset_id,name,created_at,updated_at) "
|
|
"VALUES(?1,'scale',0,0)", -1, &scan, NULL) == SQLITE_OK &&
|
|
sqlite3_prepare_v2(connection,
|
|
"INSERT INTO images(image_id,scanset_id,asset_id,original_name,"
|
|
"source_path,imported_at) VALUES(?1,?2,?3,'scale','scale',0)", -1,
|
|
&image, NULL) == SQLITE_OK &&
|
|
sqlite3_prepare_v2(connection,
|
|
"INSERT INTO feature_sets(feature_set_id,image_id,feature_asset_id,"
|
|
"extractor_kind,extractor_version,parameter_fingerprint,"
|
|
"source_image_sha256,feature_count,descriptor_type,"
|
|
"descriptor_dimension,created_at) VALUES(?1,?2,?3,?4,?5,?6,?7,"
|
|
"?8,?9,?10,0)", -1, &feature, NULL) == SQLITE_OK;
|
|
for (uint64_t i = 0; i < count && ok; ++i) {
|
|
sqlite3_int64 id = (sqlite3_int64)(first + i);
|
|
sqlite3_bind_int64(scan, 1, id);
|
|
ok = sqlite3_step(scan) == SQLITE_DONE;
|
|
sqlite3_reset(scan);
|
|
sqlite3_clear_bindings(scan);
|
|
sqlite3_bind_int64(image, 1, id);
|
|
sqlite3_bind_int64(image, 2, id);
|
|
sqlite3_bind_int64(image, 3, (sqlite3_int64)image_asset_id);
|
|
ok = ok && sqlite3_step(image) == SQLITE_DONE;
|
|
sqlite3_reset(image);
|
|
sqlite3_clear_bindings(image);
|
|
sqlite3_bind_int64(feature, 1, id);
|
|
sqlite3_bind_int64(feature, 2, id);
|
|
sqlite3_bind_int64(feature, 3, (sqlite3_int64)feature_asset_id);
|
|
sqlite3_bind_text(feature, 4, prototype->extractor_kind, -1, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(feature, 5, prototype->extractor_version);
|
|
sqlite3_bind_blob(feature, 6, prototype->parameter_fingerprint, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_blob(feature, 7, prototype->source_image_sha256, 32, SQLITE_TRANSIENT);
|
|
sqlite3_bind_int64(feature, 8, prototype->feature_count);
|
|
sqlite3_bind_int64(feature, 9, prototype->descriptor_type);
|
|
sqlite3_bind_int64(feature, 10, prototype->descriptor_dimension);
|
|
ok = ok && sqlite3_step(feature) == SQLITE_DONE;
|
|
sqlite3_reset(feature);
|
|
sqlite3_clear_bindings(feature);
|
|
}
|
|
sqlite3_finalize(scan);
|
|
sqlite3_finalize(image);
|
|
sqlite3_finalize(feature);
|
|
ok = ok && sqlite3_exec(connection, "COMMIT", NULL, NULL, NULL) == SQLITE_OK;
|
|
if (!ok) {
|
|
sqlite3_exec(connection, "ROLLBACK", NULL, NULL, NULL);
|
|
}
|
|
return sqlite3_close(connection) == SQLITE_OK && ok;
|
|
}
|
|
|
|
static bool run_test(void) {
|
|
char root[] = "/tmp/lardon3d-visual-index-XXXXXX";
|
|
CHECK(mkdtemp(root));
|
|
char internal[PATH_MAX];
|
|
char database_path[PATH_MAX];
|
|
char source_a[PATH_MAX];
|
|
char source_b[PATH_MAX];
|
|
char source_c[PATH_MAX];
|
|
char scene_a[PATH_MAX];
|
|
char scene_b[PATH_MAX];
|
|
char scene_c[PATH_MAX];
|
|
char product_paths[6][PATH_MAX];
|
|
CHECK(join_path(internal, root, ".lardon3d") && mkdir(internal, 0700) == 0 &&
|
|
join_path(database_path, root, "project.db") && join_path(source_a, root, "a.bin") &&
|
|
join_path(source_b, root, "b.bin") && join_path(source_c, root, "c.bin") &&
|
|
join_path(scene_a, root, "scene-a.pgm") && join_path(scene_b, root, "scene-b.pgm") &&
|
|
join_path(scene_c, root, "scene-c.pgm") &&
|
|
join_path(product_paths[0], root, "product-a1.pgm") &&
|
|
join_path(product_paths[1], root, "product-a2.pgm") &&
|
|
join_path(product_paths[2], root, "product-a3.pgm") &&
|
|
join_path(product_paths[3], root, "product-b1-crop.pgm") &&
|
|
join_path(product_paths[4], root, "product-b2-rotation.pgm") &&
|
|
join_path(product_paths[5], root, "product-b3.pgm") && write_source(source_a, 1) &&
|
|
write_source(source_b, 2) && write_source(source_c, 3) && write_scene(scene_a, 0, 11) &&
|
|
write_scene(scene_b, 5, 11) && write_scene(scene_c, 0, 97) &&
|
|
write_scene(product_paths[0], 0, 21) && write_scene(product_paths[1], 0, 43) &&
|
|
write_scene(product_paths[2], 0, 89) &&
|
|
write_transformed_scene(product_paths[3], 21, true, 0.0) &&
|
|
write_transformed_scene(product_paths[4], 43, false, 8.0) &&
|
|
write_scene(product_paths[5], 0, 127));
|
|
Lardon3DProjectDb *database = NULL;
|
|
char error[256];
|
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
|
Lardon3DAppState state;
|
|
lardon3d_app_state_init(&state);
|
|
state.project_loaded = true;
|
|
state.project_db = database;
|
|
CHECK(snprintf(state.project_path, sizeof(state.project_path), "%s", root) > 0);
|
|
Lardon3DProjectDbScanSet scan_a;
|
|
Lardon3DProjectDbScanSet scan_b;
|
|
CHECK(lardon3d_image_catalog_create_scanset(&state, "A", &scan_a) &&
|
|
lardon3d_image_catalog_create_scanset(&state, "B", &scan_b));
|
|
Lardon3DProjectDbImage image_a;
|
|
Lardon3DProjectDbImage image_b;
|
|
Lardon3DProjectDbImage image_c;
|
|
Lardon3DProjectDbImage image_a_copy;
|
|
Lardon3DProjectDbImage empty_image;
|
|
Lardon3DProjectDbImageAsset asset;
|
|
CHECK(lardon3d_image_catalog_import_file(&state, scan_a.scanset_id, source_a, 0, &image_a,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED &&
|
|
lardon3d_image_catalog_import_file(&state, scan_b.scanset_id, source_b, 0, &image_b,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED &&
|
|
lardon3d_image_catalog_import_file(&state, scan_b.scanset_id, source_c, 0, &image_c,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED &&
|
|
lardon3d_image_catalog_import_file(&state, scan_b.scanset_id, source_a, 0, &image_a_copy,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED &&
|
|
image_a_copy.asset_id == image_a.asset_id &&
|
|
lardon3d_image_catalog_import_file(&state, scan_a.scanset_id, source_c, 0, &empty_image,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED);
|
|
Lardon3DProjectDbFeatureSet feature_a;
|
|
Lardon3DProjectDbFeatureSet feature_b;
|
|
Lardon3DProjectDbFeatureSet feature_c;
|
|
Lardon3DProjectDbFeatureSet feature_a_copy;
|
|
Lardon3DProjectDbFeatureSet empty_feature;
|
|
CHECK(publish_features(&state, image_a.image_id, 0, &feature_a) &&
|
|
publish_features(&state, image_b.image_id, 1, &feature_b) &&
|
|
publish_features(&state, image_c.image_id, 2, &feature_c) &&
|
|
publish_features(&state, image_a_copy.image_id, 0, &feature_a_copy) &&
|
|
publish_empty_features(&state, empty_image.image_id, &empty_feature));
|
|
Lardon3DProjectDbFeatureSet burst_query;
|
|
Lardon3DProjectDbFeatureSet burst_pattern;
|
|
Lardon3DProjectDbFeatureSet burst_related;
|
|
CHECK(publish_burst_features(&state, image_a.image_id, 0, &burst_query) &&
|
|
publish_burst_features(&state, image_b.image_id, 1, &burst_pattern) &&
|
|
publish_burst_features(&state, image_c.image_id, 2, &burst_related));
|
|
Lardon3DVisualIndexConfiguration burst_configuration = {1, 16, 8};
|
|
uint64_t burst_index = 0;
|
|
uint64_t last = 0;
|
|
size_t indexed = 0;
|
|
CHECK(lardon3d_visual_index_create(database, &burst_query, &burst_configuration, &burst_index) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
lardon3d_visual_index_update_once(root, database, burst_index, 0, 0, 16, &last,
|
|
&indexed) == LARDON3D_VISUAL_INDEX_OK &&
|
|
indexed == 3);
|
|
Lardon3DVisualIndexCandidate candidates[LARDON3D_VISUAL_INDEX_TOP_K_MAX];
|
|
size_t count = 0;
|
|
Lardon3DVisualIndexQueryOptions options = {
|
|
.top_k = 4,
|
|
.minimum_evidence_count = 1,
|
|
.scanset_filter = LARDON3D_VISUAL_INDEX_ANY_SCANSET,
|
|
};
|
|
CHECK(lardon3d_visual_index_query(root, database, burst_index, burst_query.feature_set_id,
|
|
&options, candidates, 4, &count) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
candidate_rank(candidates, count, burst_related.feature_set_id) != SIZE_MAX &&
|
|
candidate_rank(candidates, count, burst_pattern.feature_set_id) == SIZE_MAX);
|
|
Lardon3DVisualIndexConfiguration configuration = {1, 16, 4096};
|
|
uint64_t index_id = 0;
|
|
CHECK(lardon3d_visual_index_create(database, &feature_a, &configuration, &index_id) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
index_id > 0);
|
|
CHECK(lardon3d_visual_index_update_once(root, database, index_id, 0, 0, 16, &last, &indexed) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
indexed == 5 && last == empty_feature.feature_set_id);
|
|
CHECK(lardon3d_visual_index_update_once(root, database, index_id, 0, last, 16, &last, &indexed) ==
|
|
LARDON3D_VISUAL_INDEX_NO_CHANGE &&
|
|
indexed == 0);
|
|
Lardon3DProjectDbFeatureSet no_pending[16];
|
|
size_t no_pending_count = 0;
|
|
CHECK(lardon3d_project_db_list_visual_index_pending(database, index_id, 0, no_pending, 16,
|
|
&no_pending_count) ==
|
|
LARDON3D_PROJECT_DB_OK &&
|
|
no_pending_count == 0);
|
|
options = (Lardon3DVisualIndexQueryOptions){
|
|
.top_k = 4,
|
|
.minimum_evidence_count = 2,
|
|
.scanset_filter = LARDON3D_VISUAL_INDEX_OTHER_SCANSETS,
|
|
};
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_OK &&
|
|
count >= 1);
|
|
bool found_related = false;
|
|
for (size_t i = 0; i < count; ++i) {
|
|
found_related = found_related ||
|
|
(candidates[i].feature_set_id == feature_b.feature_set_id &&
|
|
candidates[i].image_id == image_b.image_id &&
|
|
candidates[i].evidence_count >= 10 && candidates[i].scanset_id ==
|
|
scan_b.scanset_id);
|
|
}
|
|
CHECK(found_related);
|
|
bool found_same_asset = false;
|
|
for (size_t i = 0; i < count; ++i) {
|
|
if (candidates[i].feature_set_id == feature_a_copy.feature_set_id) {
|
|
found_same_asset = candidates[i].same_image_asset && candidates[i].evidence_count == 16 &&
|
|
candidates[i].score == 1.0;
|
|
}
|
|
}
|
|
CHECK(found_same_asset);
|
|
options.exclude_same_asset = true;
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_OK);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
CHECK(candidates[i].feature_set_id != feature_a_copy.feature_set_id);
|
|
}
|
|
options.exclude_same_asset = false;
|
|
options.scanset_filter = LARDON3D_VISUAL_INDEX_ANY_SCANSET;
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, empty_feature.feature_set_id,
|
|
&options, candidates, 4, &count) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
count == 0);
|
|
options.scanset_filter = LARDON3D_VISUAL_INDEX_SAME_SCANSET;
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_OK &&
|
|
count == 0);
|
|
|
|
Lardon3DProjectDbImage real_a;
|
|
Lardon3DProjectDbImage real_b;
|
|
Lardon3DProjectDbImage real_c;
|
|
CHECK(lardon3d_image_catalog_import_file(&state, scan_a.scanset_id, scene_a, 0, &real_a,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED &&
|
|
lardon3d_image_catalog_import_file(&state, scan_b.scanset_id, scene_b, 0, &real_b,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED &&
|
|
lardon3d_image_catalog_import_file(&state, scan_b.scanset_id, scene_c, 0, &real_c,
|
|
&asset) == LARDON3D_IMAGE_CATALOG_IMPORTED);
|
|
Lardon3DProjectDbFeatureSet real_feature_a;
|
|
Lardon3DProjectDbFeatureSet real_feature_b;
|
|
Lardon3DProjectDbFeatureSet real_feature_c;
|
|
CHECK(publish_real_features(&state, real_a.image_id, scene_a, &real_feature_a) &&
|
|
publish_real_features(&state, real_b.image_id, scene_b, &real_feature_b) &&
|
|
publish_real_features(&state, real_c.image_id, scene_c, &real_feature_c));
|
|
uint64_t real_index = 0;
|
|
Lardon3DVisualIndexConfiguration real_configuration = {1, 512, 256};
|
|
CHECK(lardon3d_visual_index_create(database, &real_feature_a, &real_configuration, &real_index) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
setenv("LARDON3D_TEST_VISUAL_INDEX_FAIL_DIR_FSYNC", "1", 1) == 0 &&
|
|
lardon3d_visual_index_update_once(root, database, real_index, 0, 0, 16, &last, &indexed) ==
|
|
LARDON3D_VISUAL_INDEX_PUBLISHED_NOT_DURABLE &&
|
|
indexed == 3 && unsetenv("LARDON3D_TEST_VISUAL_INDEX_FAIL_DIR_FSYNC") == 0);
|
|
options = (Lardon3DVisualIndexQueryOptions){8, 2, LARDON3D_VISUAL_INDEX_OTHER_SCANSETS, false};
|
|
CHECK(lardon3d_visual_index_query(root, database, real_index, real_feature_a.feature_set_id,
|
|
&options, candidates, 8, &count) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
count > 0 && candidates[0].feature_set_id == real_feature_b.feature_set_id);
|
|
Lardon3DProjectDbImage product_images[6];
|
|
Lardon3DProjectDbFeatureSet product_features[6];
|
|
for (size_t i = 0; i < 6; ++i) {
|
|
uint64_t scanset = i < 3 ? scan_a.scanset_id : scan_b.scanset_id;
|
|
CHECK(lardon3d_image_catalog_import_file(&state, scanset, product_paths[i], 0,
|
|
&product_images[i], &asset) ==
|
|
LARDON3D_IMAGE_CATALOG_IMPORTED &&
|
|
publish_product_features(&state, product_images[i].image_id, product_paths[i],
|
|
&product_features[i]));
|
|
}
|
|
Lardon3DVisualIndexConfiguration product_configuration = {1, 640, 256};
|
|
uint64_t product_index = 0;
|
|
CHECK(lardon3d_visual_index_create(database, &product_features[0], &product_configuration,
|
|
&product_index) == LARDON3D_VISUAL_INDEX_OK &&
|
|
lardon3d_visual_index_update_once(root, database, product_index, 0, 0, 16, &last,
|
|
&indexed) == LARDON3D_VISUAL_INDEX_OK &&
|
|
indexed == 6);
|
|
options = (Lardon3DVisualIndexQueryOptions){6, 1, LARDON3D_VISUAL_INDEX_ANY_SCANSET, false};
|
|
CHECK(lardon3d_visual_index_query(root, database, product_index,
|
|
product_features[0].feature_set_id, &options, candidates, 6,
|
|
&count) == LARDON3D_VISUAL_INDEX_OK);
|
|
size_t crop_rank = candidate_rank(candidates, count, product_features[3].feature_set_id);
|
|
size_t unrelated_rank = candidate_rank(candidates, count, product_features[5].feature_set_id);
|
|
CHECK(crop_rank != SIZE_MAX && (unrelated_rank == SIZE_MAX || crop_rank < unrelated_rank));
|
|
size_t low_evidence_count = count;
|
|
options.minimum_evidence_count = 8;
|
|
CHECK(lardon3d_visual_index_query(root, database, product_index,
|
|
product_features[0].feature_set_id, &options, candidates, 6,
|
|
&count) == LARDON3D_VISUAL_INDEX_OK &&
|
|
count <= low_evidence_count);
|
|
options = (Lardon3DVisualIndexQueryOptions){6, 1,
|
|
LARDON3D_VISUAL_INDEX_OTHER_SCANSETS, false};
|
|
CHECK(lardon3d_visual_index_query(root, database, product_index,
|
|
product_features[0].feature_set_id, &options, candidates, 6,
|
|
&count) == LARDON3D_VISUAL_INDEX_OK);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
CHECK(candidates[i].scanset_id == scan_b.scanset_id);
|
|
}
|
|
options.scanset_filter = LARDON3D_VISUAL_INDEX_SAME_SCANSET;
|
|
CHECK(lardon3d_visual_index_query(root, database, product_index,
|
|
product_features[0].feature_set_id, &options, candidates, 6,
|
|
&count) == LARDON3D_VISUAL_INDEX_OK);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
CHECK(candidates[i].scanset_id == scan_a.scanset_id);
|
|
}
|
|
options.scanset_filter = LARDON3D_VISUAL_INDEX_ANY_SCANSET;
|
|
CHECK(lardon3d_visual_index_query(root, database, product_index,
|
|
product_features[1].feature_set_id, &options, candidates, 6,
|
|
&count) == LARDON3D_VISUAL_INDEX_OK);
|
|
size_t rotation_rank = candidate_rank(candidates, count, product_features[4].feature_set_id);
|
|
unrelated_rank = candidate_rank(candidates, count, product_features[5].feature_set_id);
|
|
CHECK(rotation_rank != SIZE_MAX &&
|
|
(unrelated_rank == SIZE_MAX || rotation_rank < unrelated_rank));
|
|
Lardon3DVisualIndexConfiguration retry_configuration = {1, 15, 64};
|
|
uint64_t retry_index = 0;
|
|
CHECK(lardon3d_visual_index_create(database, &feature_a, &retry_configuration, &retry_index) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
setenv("LARDON3D_TEST_PROJECT_DB_FAIL_VISUAL_SEGMENT", "1", 1) == 0 &&
|
|
lardon3d_visual_index_update_once(root, database, retry_index, 0, 0, 16, &last, &indexed) ==
|
|
LARDON3D_VISUAL_INDEX_DB_BUSY &&
|
|
unsetenv("LARDON3D_TEST_PROJECT_DB_FAIL_VISUAL_SEGMENT") == 0 &&
|
|
lardon3d_visual_index_update_once(root, database, retry_index, 0, 0, 16, &last, &indexed) ==
|
|
LARDON3D_VISUAL_INDEX_OK &&
|
|
indexed == 5);
|
|
Lardon3DVisualIndexConfiguration race_configuration = {1, 14, 64};
|
|
uint64_t race_index = 0;
|
|
CHECK(lardon3d_visual_index_create(database, &feature_a, &race_configuration, &race_index) ==
|
|
LARDON3D_VISUAL_INDEX_OK);
|
|
UpdateRace race = {.root = root, .database = database, .index_id = race_index};
|
|
UpdateRaceThread race_threads[2] = {{&race, 0}, {&race, 1}};
|
|
pthread_t update_threads[2];
|
|
CHECK(pthread_barrier_init(&race.barrier, NULL, 2) == 0 &&
|
|
pthread_create(&update_threads[0], NULL, update_race_thread, &race_threads[0]) == 0 &&
|
|
pthread_create(&update_threads[1], NULL, update_race_thread, &race_threads[1]) == 0 &&
|
|
pthread_join(update_threads[0], NULL) == 0 && pthread_join(update_threads[1], NULL) == 0 &&
|
|
pthread_barrier_destroy(&race.barrier) == 0);
|
|
CHECK((race.result[0] == LARDON3D_VISUAL_INDEX_OK) !=
|
|
(race.result[1] == LARDON3D_VISUAL_INDEX_OK));
|
|
CHECK(race.result[0] == LARDON3D_VISUAL_INDEX_OK ||
|
|
race.result[0] == LARDON3D_VISUAL_INDEX_DB_ERROR ||
|
|
race.result[0] == LARDON3D_VISUAL_INDEX_DB_BUSY);
|
|
CHECK(race.result[1] == LARDON3D_VISUAL_INDEX_OK ||
|
|
race.result[1] == LARDON3D_VISUAL_INDEX_DB_ERROR ||
|
|
race.result[1] == LARDON3D_VISUAL_INDEX_DB_BUSY);
|
|
CHECK(lardon3d_visual_index_update_once(root, database, race_index, 0, 0, 16, &last,
|
|
&indexed) == LARDON3D_VISUAL_INDEX_NO_CHANGE);
|
|
options.top_k = LARDON3D_VISUAL_INDEX_TOP_K_MAX + 1;
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) ==
|
|
LARDON3D_VISUAL_INDEX_INVALID_ARGUMENT);
|
|
QueryThread contexts[4];
|
|
pthread_t threads[4];
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
contexts[i] = (QueryThread){root, database, index_id, feature_a.feature_set_id, false};
|
|
CHECK(pthread_create(&threads[i], NULL, query_thread, &contexts[i]) == 0);
|
|
}
|
|
for (size_t i = 0; i < 4; ++i) {
|
|
CHECK(pthread_join(threads[i], NULL) == 0 && contexts[i].ok);
|
|
}
|
|
Lardon3DProjectDbVisualIndexSegment segments[2];
|
|
size_t segment_count = 0;
|
|
CHECK(lardon3d_project_db_list_visual_index_segments(database, index_id, 0, segments, 2,
|
|
&segment_count) == LARDON3D_PROJECT_DB_OK &&
|
|
segment_count == 1);
|
|
char segment_path[PATH_MAX];
|
|
CHECK(join_path(segment_path, root, segments[0].path));
|
|
unsigned char *segment_bytes = malloc((size_t)segments[0].size_bytes);
|
|
CHECK(segment_bytes);
|
|
int descriptor = open(segment_path, O_RDWR | O_CLOEXEC);
|
|
unsigned char original;
|
|
CHECK(descriptor >= 0 &&
|
|
pread(descriptor, segment_bytes, (size_t)segments[0].size_bytes, 0) ==
|
|
(ssize_t)segments[0].size_bytes &&
|
|
pread(descriptor, &original, 1, 12) == 1);
|
|
unsigned char original_hash[32];
|
|
char original_path[PATH_MAX];
|
|
memcpy(original_hash, segments[0].sha256, sizeof(original_hash));
|
|
CHECK(snprintf(original_path, sizeof(original_path), "%s", segments[0].path) > 0);
|
|
options = (Lardon3DVisualIndexQueryOptions){4, 1, LARDON3D_VISUAL_INDEX_ANY_SCANSET, false};
|
|
unsigned char *coherent = malloc((size_t)segments[0].size_bytes);
|
|
char coherent_path[PATH_MAX];
|
|
unsigned char coherent_hash[32];
|
|
CHECK(coherent);
|
|
memcpy(coherent, segment_bytes, (size_t)segments[0].size_bytes);
|
|
coherent[8] = 2;
|
|
coherent[9] = coherent[10] = coherent[11] = 0;
|
|
CHECK(publish_mutated_segment(root, database_path, &segments[0], coherent,
|
|
(size_t)segments[0].size_bytes, coherent_path, coherent_hash) &&
|
|
lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) ==
|
|
LARDON3D_VISUAL_INDEX_UNSUPPORTED_VERSION &&
|
|
replace_segment_metadata(database_path, segments[0].visual_index_segment_id,
|
|
original_hash, original_path, segments[0].size_bytes));
|
|
memcpy(coherent, segment_bytes, (size_t)segments[0].size_bytes);
|
|
memset(coherent + 24, 0xff, 8);
|
|
CHECK(publish_mutated_segment(root, database_path, &segments[0], coherent,
|
|
(size_t)segments[0].size_bytes, coherent_path, coherent_hash) &&
|
|
lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_CORRUPT &&
|
|
replace_segment_metadata(database_path, segments[0].visual_index_segment_id,
|
|
original_hash, original_path, segments[0].size_bytes));
|
|
memcpy(coherent, segment_bytes, (size_t)segments[0].size_bytes);
|
|
uint64_t overflow_count = (UINT64_MAX - 128U) / 24U + 1U;
|
|
for (size_t i = 0; i < 8; ++i) {
|
|
coherent[24 + i] = (unsigned char)(overflow_count >> (i * 8));
|
|
}
|
|
CHECK(publish_mutated_segment(root, database_path, &segments[0], coherent,
|
|
(size_t)segments[0].size_bytes, coherent_path, coherent_hash) &&
|
|
lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_CORRUPT &&
|
|
replace_segment_metadata(database_path, segments[0].visual_index_segment_id,
|
|
original_hash, original_path, segments[0].size_bytes));
|
|
free(coherent);
|
|
unsigned char mutated = (unsigned char)(original ^ 0x40U);
|
|
CHECK(pwrite(descriptor, &mutated, 1, 12) == 1 && close(descriptor) == 0);
|
|
options = (Lardon3DVisualIndexQueryOptions){4, 1, LARDON3D_VISUAL_INDEX_ANY_SCANSET, false};
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_CORRUPT);
|
|
descriptor = open(segment_path, O_RDWR | O_CLOEXEC);
|
|
CHECK(descriptor >= 0 && pwrite(descriptor, &original, 1, 12) == 1 && close(descriptor) == 0);
|
|
char hidden_path[PATH_MAX];
|
|
CHECK(snprintf(hidden_path, sizeof(hidden_path), "%s.hidden", segment_path) > 0 &&
|
|
rename(segment_path, hidden_path) == 0);
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_NOT_FOUND &&
|
|
rename(hidden_path, segment_path) == 0);
|
|
descriptor = open(segment_path, O_RDWR | O_CLOEXEC);
|
|
CHECK(descriptor >= 0 && ftruncate(descriptor, (off_t)segments[0].size_bytes - 1) == 0 &&
|
|
close(descriptor) == 0);
|
|
CHECK(lardon3d_visual_index_query(root, database, index_id, feature_a.feature_set_id, &options,
|
|
candidates, 4, &count) == LARDON3D_VISUAL_INDEX_CORRUPT);
|
|
descriptor = open(segment_path, O_WRONLY | O_TRUNC | O_CLOEXEC);
|
|
CHECK(descriptor >= 0 &&
|
|
write(descriptor, segment_bytes, (size_t)segments[0].size_bytes) ==
|
|
(ssize_t)segments[0].size_bytes &&
|
|
close(descriptor) == 0);
|
|
free(segment_bytes);
|
|
lardon3d_project_db_close(database);
|
|
database = NULL;
|
|
state.project_db = NULL;
|
|
CHECK(insert_scale_fixture(database_path, image_a.asset_id, feature_a.feature_asset_id,
|
|
&feature_a, 10000, 50000));
|
|
CHECK(lardon3d_project_db_open(database_path, &database, error) == LARDON3D_PROJECT_DB_OK);
|
|
state.project_db = database;
|
|
Lardon3DVisualIndexConfiguration capacity_configuration = {1, 15, 4096};
|
|
uint64_t capacity_index = 0;
|
|
CHECK(lardon3d_visual_index_create(database, &feature_a, &capacity_configuration,
|
|
&capacity_index) == LARDON3D_VISUAL_INDEX_OK);
|
|
size_t total_indexed = 0;
|
|
last = 0;
|
|
for (size_t batch = 0; batch < LARDON3D_VISUAL_INDEX_SEGMENT_MAX; ++batch) {
|
|
Lardon3DVisualIndexResult updated =
|
|
lardon3d_visual_index_update_once(root, database, capacity_index, 0, last, 16, &last,
|
|
&indexed);
|
|
CHECK(updated == LARDON3D_VISUAL_INDEX_OK);
|
|
total_indexed += indexed;
|
|
}
|
|
CHECK(total_indexed == 4096 &&
|
|
lardon3d_visual_index_update_once(root, database, capacity_index, 0, last, 16, &last,
|
|
&indexed) == LARDON3D_VISUAL_INDEX_LIMIT);
|
|
Lardon3DProjectDbFeatureSet pending[16];
|
|
size_t pending_count = 0;
|
|
CHECK(lardon3d_project_db_list_visual_index_pending(database, capacity_index, last, pending, 16,
|
|
&pending_count) ==
|
|
LARDON3D_PROJECT_DB_OK &&
|
|
pending_count == 16);
|
|
options = (Lardon3DVisualIndexQueryOptions){10, 1, LARDON3D_VISUAL_INDEX_ANY_SCANSET, false};
|
|
CHECK(lardon3d_visual_index_query(root, database, capacity_index, feature_a.feature_set_id,
|
|
&options,
|
|
candidates, 10, &count) == LARDON3D_VISUAL_INDEX_OK &&
|
|
count == 10);
|
|
lardon3d_project_db_close(database);
|
|
CHECK(remove_tree(root));
|
|
return true;
|
|
}
|
|
|
|
int main(void) { return run_test() ? 0 : 1; }
|