lardon3d/tests/test_matcher.c

518 lines
17 KiB
C

#include <errno.h>
#include <fcntl.h>
#include <math.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/match_file.h>
#include <lardon3d/matcher.h>
#define CHECK(condition) \
do { \
if (!(condition)) { \
fprintf(stderr, "Échec ligne %d : %s\n", __LINE__, #condition); \
return false; \
} \
} while (0)
/* --- Match File tests --- */
static bool test_match_file_write_read(void) {
char dir[] = "/tmp/lardon3d-mf-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/test.mf", dir) > 0);
Lardon3DMatchFileEntry entries[3];
entries[0].feature_index_a = 0;
entries[0].feature_index_b = 0;
entries[0].distance = 0.0F;
entries[1].feature_index_a = 5;
entries[1].feature_index_b = 3;
entries[1].distance = 3.0F;
entries[2].feature_index_a = 10;
entries[2].feature_index_b = 20;
entries[2].distance = 12.5F;
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(lardon3d_match_file_write(fd, 1, 32, 1, 2, entries, 3) ==
LARDON3D_MATCH_FILE_OK);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 8192, 8192) ==
LARDON3D_MATCH_FILE_OK);
CHECK(memcmp(header.magic, LARDON3D_MATCH_FILE_MAGIC, 4) == 0);
CHECK(header.format_version == 1);
CHECK(header.descriptor_type == 1);
CHECK(header.descriptor_dimension == 32);
CHECK(header.match_count == 3);
CHECK(header.feature_set_id_a == 1 && header.feature_set_id_b == 2);
fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
CHECK(fd >= 0);
unsigned char physical_magic[4];
CHECK(read(fd, physical_magic, sizeof(physical_magic)) == (ssize_t)sizeof(physical_magic));
CHECK(memcmp(physical_magic, "L3DM", 4) == 0);
CHECK(lseek(fd, 0, SEEK_SET) == 0);
Lardon3DMatchFileEntry read_entries[10];
uint32_t count = 0;
CHECK(lardon3d_match_file_read(fd, &header, read_entries, 10, &count, 1, 2, 8192, 8192) ==
LARDON3D_MATCH_FILE_OK);
(void)close(fd);
CHECK(count == 3);
CHECK(read_entries[0].feature_index_a == 0);
CHECK(read_entries[2].feature_index_a == 10);
CHECK(read_entries[2].feature_index_b == 20);
CHECK(fabsf(read_entries[2].distance - 12.5F) < 0.001F);
CHECK(lardon3d_match_file_validate(path, &header, 9, 2, 8192, 8192) ==
LARDON3D_MATCH_FILE_CORRUPT);
CHECK(lardon3d_match_file_validate(path, &header, 1, 9, 8192, 8192) ==
LARDON3D_MATCH_FILE_CORRUPT);
CHECK(lardon3d_match_file_validate(path, &header, 2, 1, 8192, 8192) ==
LARDON3D_MATCH_FILE_CORRUPT);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_empty(void) {
char dir[] = "/tmp/lardon3d-me-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/empty.mf", dir) > 0);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(lardon3d_match_file_write(fd, 1, 32, 1, 2, NULL, 0) ==
LARDON3D_MATCH_FILE_OK);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 0, 0) ==
LARDON3D_MATCH_FILE_OK);
CHECK(header.match_count == 0);
fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
CHECK(fd >= 0);
Lardon3DMatchFileEntry entries[1];
uint32_t count = 0;
CHECK(lardon3d_match_file_read(fd, &header, entries, 1, &count, 1, 2, 0, 0) ==
LARDON3D_MATCH_FILE_OK);
(void)close(fd);
CHECK(count == 0);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_bad_magic(void) {
char dir[] = "/tmp/lardon3d-mbm-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/bad.mf", dir) > 0);
unsigned char buf[32] = {0};
memcpy(buf, "MD3L", 4);
buf[4] = 1;
buf[5] = 1;
buf[12] = 32;
buf[16] = 1;
buf[24] = 2;
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(write(fd, buf, 32) == 32);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 0, 0, 0, 0) ==
LARDON3D_MATCH_FILE_BAD_MAGIC);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_bad_version(void) {
unsigned char buf[32] = {0};
memcpy(buf, "L3DM", 4);
buf[4] = 42;
char dir[] = "/tmp/lardon3d-mbv-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/badv.mf", dir) > 0);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(write(fd, buf, 32) == 32);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 0, 0, 0, 0) ==
LARDON3D_MATCH_FILE_BAD_VERSION);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_truncated(void) {
unsigned char buf[32] = {0};
memcpy(buf, "L3DM", 4);
buf[4] = 1;
buf[5] = 1;
buf[8] = 1;
buf[12] = 32;
buf[16] = 1;
buf[24] = 2;
char dir[] = "/tmp/lardon3d-mt-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/trunc.mf", dir) > 0);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(write(fd, buf, 32) == 32);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 0, 0, 0, 0) ==
LARDON3D_MATCH_FILE_BAD_SIZE);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_invalid_entry(void) {
char dir[] = "/tmp/lardon3d-mie-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/bade.mf", dir) > 0);
unsigned char buf[44];
memset(buf, 0, 44);
memcpy(buf, "L3DM", 4);
buf[4] = 1;
buf[5] = 1;
buf[8] = 1;
buf[12] = 32;
buf[16] = 1;
buf[24] = 2;
buf[32] = 0x0F;
buf[33] = 0x27;
buf[34] = 0;
buf[35] = 0;
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(write(fd, buf, 44) == 44);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 8192, 8192) ==
LARDON3D_MATCH_FILE_BAD_ENTRY);
fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
CHECK(fd >= 0);
Lardon3DMatchFileEntry entries[1];
uint32_t count = 0;
CHECK(lardon3d_match_file_read(fd, &header, entries, 1, &count, 1, 2, 8192, 8192) ==
LARDON3D_MATCH_FILE_BAD_ENTRY);
(void)close(fd);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_nan_distance(void) {
char dir[] = "/tmp/lardon3d-mnd-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/nan.mf", dir) > 0);
unsigned char buf[44];
memset(buf, 0, 44);
memcpy(buf, "L3DM", 4);
buf[4] = 1;
buf[5] = 1;
buf[8] = 1;
buf[12] = 32;
buf[16] = 1;
buf[24] = 2;
buf[40] = 0;
buf[41] = 0;
buf[42] = 0xC0;
buf[43] = 0x7F;
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(write(fd, buf, 44) == 44);
(void)close(fd);
fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
CHECK(fd >= 0);
Lardon3DMatchFileHeader header;
Lardon3DMatchFileEntry entries[1];
uint32_t count = 0;
CHECK(lardon3d_match_file_read(fd, &header, entries, 1, &count, 1, 2, 8192, 8192) ==
LARDON3D_MATCH_FILE_BAD_ENTRY);
(void)close(fd);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_f32(void) {
char dir[] = "/tmp/lardon3d-mf32-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/sift.mf", dir) > 0);
Lardon3DMatchFileEntry entries[1];
entries[0].feature_index_a = 0;
entries[0].feature_index_b = 0;
entries[0].distance = 100.0F;
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(lardon3d_match_file_write(fd, 2, 128, 1, 2, entries, 1) ==
LARDON3D_MATCH_FILE_OK);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 8192, 8192) ==
LARDON3D_MATCH_FILE_OK);
CHECK(header.descriptor_type == 2);
CHECK(header.descriptor_dimension == 128);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_bad_type(void) {
unsigned char buf[32] = {0};
memcpy(buf, "L3DM", 4);
buf[4] = 1;
buf[5] = 3;
char dir[] = "/tmp/lardon3d-mbt-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/badtype.mf", dir) > 0);
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(write(fd, buf, 32) == 32);
(void)close(fd);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 0, 0, 0, 0) ==
LARDON3D_MATCH_FILE_BAD_TYPE);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool test_match_file_maximum_and_multiplicity(void) {
char dir[] = "/tmp/lardon3d-mmax-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/max.mf", dir) > 0);
Lardon3DMatchFileEntry *entries = calloc(LARDON3D_MATCH_FILE_MAX_MATCHES,
sizeof(*entries));
CHECK(entries);
for (uint32_t i = 0; i < LARDON3D_MATCH_FILE_MAX_MATCHES; ++i) {
entries[i].feature_index_a = i;
entries[i].feature_index_b = 0;
entries[i].distance = (float)i;
}
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0);
CHECK(lardon3d_match_file_write(fd, 1, 32, 11, 12, entries,
LARDON3D_MATCH_FILE_MAX_MATCHES) ==
LARDON3D_MATCH_FILE_OK);
CHECK(lardon3d_match_file_write(fd, 1, 32, 11, 12, entries,
LARDON3D_MATCH_FILE_MAX_MATCHES + 1) ==
LARDON3D_MATCH_FILE_INVALID_ARGUMENT);
CHECK(close(fd) == 0);
struct stat info;
CHECK(stat(path, &info) == 0 && info.st_size == LARDON3D_MATCH_FILE_MAX_SIZE);
Lardon3DMatchFileHeader header;
CHECK(lardon3d_match_file_validate(path, &header, 11, 12, 8192, 1) ==
LARDON3D_MATCH_FILE_OK);
free(entries);
CHECK(unlink(path) == 0);
CHECK(rmdir(dir) == 0);
return true;
}
static bool write_minimal_match_file(const char *path) {
Lardon3DMatchFileEntry entry = {0, 0, 1.0F};
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
if (fd < 0) return false;
bool ok = lardon3d_match_file_write(fd, 1, 32, 1, 2, &entry, 1) ==
LARDON3D_MATCH_FILE_OK;
return close(fd) == 0 && ok;
}
static bool mutate_bytes(const char *path, off_t offset, const void *bytes, size_t size) {
int fd = open(path, O_WRONLY | O_CLOEXEC);
if (fd < 0) return false;
bool ok = pwrite(fd, bytes, size, offset) == (ssize_t)size;
return close(fd) == 0 && ok;
}
static bool expect_mutation(const char *path, off_t offset, const void *bytes, size_t size,
Lardon3DMatchFileResult expected) {
if (!write_minimal_match_file(path) || !mutate_bytes(path, offset, bytes, size)) return false;
Lardon3DMatchFileHeader header;
return lardon3d_match_file_validate(path, &header, 1, 2, 1, 1) == expected;
}
static bool test_match_file_corruption_matrix(void) {
char dir[] = "/tmp/lardon3d-matrix-XXXXXX";
CHECK(mkdtemp(dir));
char path[512];
CHECK(snprintf(path, sizeof(path), "%s/match", dir) > 0);
Lardon3DMatchFileHeader header;
int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0 && close(fd) == 0);
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 1, 1) ==
LARDON3D_MATCH_FILE_BAD_SIZE);
unsigned char one = 0;
CHECK(mutate_bytes(path, 0, &one, 1));
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 1, 1) ==
LARDON3D_MATCH_FILE_BAD_SIZE);
unsigned char version_zero = 0;
unsigned char version_future = 2;
unsigned char type_zero = 0;
unsigned char type_unknown = 99;
unsigned char reserved[2] = {1, 0};
unsigned char dimension[4] = {31, 0, 0, 0};
unsigned char zero_id[8] = {0};
unsigned char count_8193[4] = {1, 32, 0, 0};
unsigned char count_max[4] = {255, 255, 255, 255};
unsigned char bad_magic[4] = {'B', 'A', 'D', '!'};
CHECK(expect_mutation(path, 0, bad_magic, 4, LARDON3D_MATCH_FILE_BAD_MAGIC));
CHECK(expect_mutation(path, 4, &version_zero, 1, LARDON3D_MATCH_FILE_BAD_VERSION));
CHECK(expect_mutation(path, 4, &version_future, 1, LARDON3D_MATCH_FILE_BAD_VERSION));
CHECK(expect_mutation(path, 5, &type_zero, 1, LARDON3D_MATCH_FILE_BAD_TYPE));
CHECK(expect_mutation(path, 5, &type_unknown, 1, LARDON3D_MATCH_FILE_BAD_TYPE));
CHECK(expect_mutation(path, 6, reserved, 2, LARDON3D_MATCH_FILE_CORRUPT));
CHECK(expect_mutation(path, 12, dimension, 4, LARDON3D_MATCH_FILE_BAD_TYPE));
CHECK(expect_mutation(path, 16, zero_id, 8, LARDON3D_MATCH_FILE_CORRUPT));
CHECK(expect_mutation(path, 24, zero_id, 8, LARDON3D_MATCH_FILE_CORRUPT));
CHECK(expect_mutation(path, 8, count_8193, 4, LARDON3D_MATCH_FILE_BAD_COUNT));
CHECK(expect_mutation(path, 8, count_max, 4, LARDON3D_MATCH_FILE_BAD_COUNT));
unsigned char index_one[4] = {1, 0, 0, 0};
CHECK(expect_mutation(path, 32, index_one, 4, LARDON3D_MATCH_FILE_BAD_ENTRY));
CHECK(expect_mutation(path, 36, index_one, 4, LARDON3D_MATCH_FILE_BAD_ENTRY));
unsigned char negative[4] = {0, 0, 128, 191};
unsigned char negative_zero[4] = {0, 0, 0, 128};
unsigned char positive_inf[4] = {0, 0, 128, 127};
unsigned char negative_inf[4] = {0, 0, 128, 255};
unsigned char nan[4] = {0, 0, 192, 127};
CHECK(expect_mutation(path, 40, negative, 4, LARDON3D_MATCH_FILE_BAD_ENTRY));
CHECK(expect_mutation(path, 40, positive_inf, 4, LARDON3D_MATCH_FILE_BAD_ENTRY));
CHECK(expect_mutation(path, 40, negative_inf, 4, LARDON3D_MATCH_FILE_BAD_ENTRY));
CHECK(expect_mutation(path, 40, nan, 4, LARDON3D_MATCH_FILE_BAD_ENTRY));
CHECK(expect_mutation(path, 40, negative_zero, 4, LARDON3D_MATCH_FILE_OK));
CHECK(write_minimal_match_file(path));
fd = open(path, O_WRONLY | O_APPEND | O_CLOEXEC);
CHECK(fd >= 0 && write(fd, "x", 1) == 1 && close(fd) == 0);
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 1, 1) ==
LARDON3D_MATCH_FILE_BAD_SIZE);
Lardon3DMatchFileEntry ordered[2] = {{0, 0, 0.0F}, {1, 0, 1.0F}};
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0 && lardon3d_match_file_write(fd, 1, 32, 1, 2, ordered, 2) ==
LARDON3D_MATCH_FILE_OK &&
close(fd) == 0);
unsigned char duplicate_a[4] = {0, 0, 0, 0};
CHECK(mutate_bytes(path, 44, duplicate_a, sizeof(duplicate_a)));
CHECK(lardon3d_match_file_validate(path, &header, 1, 2, 2, 1) ==
LARDON3D_MATCH_FILE_BAD_ENTRY);
ordered[1].feature_index_a = 0;
fd = open(path, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0644);
CHECK(fd >= 0 && lardon3d_match_file_write(fd, 1, 32, 1, 2, ordered, 2) ==
LARDON3D_MATCH_FILE_BAD_ENTRY &&
close(fd) == 0);
CHECK(unlink(path) == 0 && rmdir(dir) == 0);
return true;
}
/* --- Matcher utility function tests --- */
static bool test_matcher_kind_string(void) {
CHECK(strcmp(lardon3d_matcher_kind_string(LARDON3D_MATCHER_ORB_BF), "orb_bf") == 0);
CHECK(strcmp(lardon3d_matcher_kind_string(LARDON3D_MATCHER_SIFT_BF), "sift_bf") == 0);
CHECK(strcmp(lardon3d_matcher_kind_string(LARDON3D_MATCHER_ROOTSIFT_BF), "rootsift_bf") == 0);
return true;
}
static bool test_matcher_fingerprint(void) {
unsigned char fp1[32], fp2[32];
Lardon3DMatcherParams p1 = {LARDON3D_MATCHER_ORB_BF, 0.75F};
Lardon3DMatcherParams p2 = {LARDON3D_MATCHER_ORB_BF, 0.80F};
lardon3d_matcher_fingerprint(&p1, fp1);
lardon3d_matcher_fingerprint(&p2, fp2);
CHECK(memcmp(fp1, fp2, 32) != 0);
unsigned char fp3[32];
lardon3d_matcher_fingerprint(&p1, fp3);
CHECK(memcmp(fp1, fp3, 32) == 0);
Lardon3DMatcherParams p3 = {LARDON3D_MATCHER_SIFT_BF, 0.75F};
unsigned char fp4[32];
lardon3d_matcher_fingerprint(&p3, fp4);
CHECK(memcmp(fp1, fp4, 32) != 0);
return true;
}
static bool test_matcher_default_ratio(void) {
float r = lardon3d_matcher_default_ratio(LARDON3D_MATCHER_ORB_BF);
CHECK(r == 0.75F);
r = lardon3d_matcher_default_ratio(LARDON3D_MATCHER_SIFT_BF);
CHECK(r == 0.7F);
r = lardon3d_matcher_default_ratio(LARDON3D_MATCHER_ROOTSIFT_BF);
CHECK(r == 0.7F);
return true;
}
static bool run_tests(void) {
CHECK(test_match_file_write_read());
CHECK(test_match_file_empty());
CHECK(test_match_file_bad_magic());
CHECK(test_match_file_bad_version());
CHECK(test_match_file_truncated());
CHECK(test_match_file_invalid_entry());
CHECK(test_match_file_nan_distance());
CHECK(test_match_file_f32());
CHECK(test_match_file_bad_type());
CHECK(test_match_file_maximum_and_multiplicity());
CHECK(test_match_file_corruption_matrix());
CHECK(test_matcher_kind_string());
CHECK(test_matcher_fingerprint());
CHECK(test_matcher_default_ratio());
return true;
}
int main(void) {
return run_tests() ? EXIT_SUCCESS : EXIT_FAILURE;
}