diff --git a/Sources/Makefile b/Sources/Makefile index 7378690..db71671 100644 --- a/Sources/Makefile +++ b/Sources/Makefile @@ -58,6 +58,7 @@ UTEST_SRCS = General/*.c General/runners/*.c \ Extraction/Filters/*.c Extraction/Filters/runners/*.c Extraction/Model/*.c Extraction/Model/runners/*.c \ DataStructures/*.c \ EndToEnd/*.c \ + Templates/*.c \ Matcher/*.c Matcher/runners/*.c UTEST_SRCS_EXCLUDE = diff --git a/Sources/Templates/Template.c b/Sources/Templates/Template.c index 927fb79..0b99dcc 100644 --- a/Sources/Templates/Template.c +++ b/Sources/Templates/Template.c @@ -4,7 +4,7 @@ #include #include -void Template_AddMinuitia(Template *template, TemplateMinutia *minutia) +void Template_AddMinutia(Template *template, TemplateMinutia *minutia) { List_AddData(&(template->minutiae), minutia); } @@ -14,7 +14,7 @@ List *Template_GetMinutiae(Template *template) return &template->minutiae; } -Template Template_Constuct() +Template Template_Construct() { Template template; template.originalDpi = 0; @@ -26,10 +26,5 @@ Template Template_Constuct() void Template_Free(Template *template) { - while (List_GetCount(&(template->minutiae)) > 0) - { - void *dataFound; - List_Remove(&(template->minutiae), (template->minutiae.tail), &dataFound); - free(dataFound); - } + List_Destroy(&(template->minutiae), &(free)); } diff --git a/Sources/Templates/Template.h b/Sources/Templates/Template.h index 8477ede..ac6129f 100644 --- a/Sources/Templates/Template.h +++ b/Sources/Templates/Template.h @@ -13,8 +13,8 @@ typedef struct Template List minutiae; } Template; -Template Template_Constuct(void); -void Template_AddMinuitia(Template *, TemplateMinutia *); +Template Template_Construct(void); +void Template_AddMinutia(Template *, TemplateMinutia *); void Template_Free(Template *); #endif diff --git a/Sources/Templates/TemplateIO.c b/Sources/Templates/TemplateIO.c new file mode 100644 index 0000000..bfecf3b --- /dev/null +++ b/Sources/Templates/TemplateIO.c @@ -0,0 +1,298 @@ +#include "General/Calc.h" +#include "Templates/TemplateIO.h" + +#include +#include +#include +#include +#include + +// Format (all numbers are big-endian): +// 4B magic "FMR\0" +// 4B version (ignored, set to " 20\0" +// 4B total length (including header) +// 2B rubbish (zeroed) +// 2B image size in pixels X +// 2B image size in pixels Y +// 2B rubbish (pixels per cm X, set to 196 = 500dpi) +// 2B rubbish (pixels per cm Y, set to 196 = 500dpi) +// 1B rubbish (number of fingerprints, set to 1) +// 1B rubbish (zeroed) +// 1B rubbish (finger position, zeroed) +// 1B rubbish (zeroed) +// 1B rubbish (fingerprint quality, set to 100) +// 1B minutia count +// N*6B minutiae +// 2B minutia position X in pixels +// 2b (upper) minutia type (01 ending, 10 bifurcation, 00 other) +// 2B minutia position Y in pixels (upper 2b ignored, zeroed) +// 1B direction, compatible with SourceAFIS angles +// 1B quality (ignored, zeroed) +// 2B rubbish (extra data length, zeroed) +// N*1B rubbish (extra data) +void TemplateIO_ISO19794_2_2005_Export(Template *template, const char *outputFileName) +{ + // Open a binary file to output the template to... + FILE *output = fopen(outputFileName, "wb"); + + // 4B magic "FMR\0" + char magic[] = {'F', 'M', 'R', '\0'}; + int count = fwrite(magic, sizeof(magic), 1, output); + assert(count == 1); + + // 4B version (ignored, set to " 20\0" + char version[] = {' ', '2', '0', '\0'}; + count = fwrite(version, sizeof(version), 1, output); + assert(count == 1); + + // 4B total length (28 bytes for the header, 6 bytes for each minutia and 2 bytes footer padding) + int32_t totalLength = 30 + (List_GetCount(&template->minutiae) * 6); + count = fwrite(&totalLength, sizeof(totalLength), 1, output); + assert(count == 1); + + // 2B rubbish (zeroed) + int16_t twoByteRubbish = 0; + count = fwrite(&twoByteRubbish, sizeof(twoByteRubbish), 1, output); + assert(count == 1); + + // 2B image size in pixels X + int16_t imageSizeX = Calc_DivRoundUp(template->originalWidth * 500, template->originalDpi); + count = fwrite(&imageSizeX, sizeof(imageSizeX), 1, output); + assert(count == 1); + + // 2B image size in pixels Y + int16_t imageSizeY = Calc_DivRoundUp(template->originalHeight * 500, template->originalDpi); + count = fwrite(&imageSizeY, sizeof(imageSizeY), 1, output); + assert(count == 1); + + // 2B rubbish (pixels per cm X, set to 196 = 500dpi) + int16_t pixelsPerCm = 196; + count = fwrite(&pixelsPerCm, sizeof(pixelsPerCm), 1, output); + assert(count == 1); + + // 2B rubbish (pixels per cm Y, set to 196 = 500dpi) + count = fwrite(&pixelsPerCm, sizeof(pixelsPerCm), 1, output); + assert(count == 1); + + // 1B rubbish (number of fingerprints, set to 1) + int8_t oneByteRubbish = 1; + count = fwrite(&oneByteRubbish, sizeof(oneByteRubbish), 1, output); + assert(count == 1); + + // 1B rubbish (zeroed) + oneByteRubbish = 0; + count = fwrite(&oneByteRubbish, sizeof(oneByteRubbish), 1, output); + assert(count == 1); + + // 1B rubbish (finger position, zeroed) + count = fwrite(&oneByteRubbish, sizeof(oneByteRubbish), 1, output); + assert(count == 1); + + // 1B rubbish (zeroed) + count = fwrite(&oneByteRubbish, sizeof(oneByteRubbish), 1, output); + assert(count == 1); + + // 1B rubbish (fingerprint quality, set to 100) + int8_t fingerprintQuality = 100; + count = fwrite(&fingerprintQuality, sizeof(fingerprintQuality), 1, output); + assert(count == 1); + + // 1B minutia count + int8_t minutiaCount = List_GetCount(&template->minutiae); + count = fwrite(&minutiaCount, sizeof(int8_t), 1, output); + assert(count == 1); + + // N*6B minutiae + for (ListElement *element = template->minutiae.head; element != NULL; element = element->next) + { + // Get the minutia from the list element... + TemplateMinutia *minutia = element->data; + + // 2B minutia position X in pixels + // 2b (upper) minutia type (01 ending, 10 bifurcation, 00 other (considered ending)) + int16_t x = minutia->position.x; + assert(x <= 0x3fff); + + uint16_t type; + switch (minutia->type) + { + case ENDING: + type = 0x4000; + break; + case BIFURCATION: + type = 0x8000; + break; + case OTHER: + type = 0; + break; + default: + assert(false); + } + uint16_t combined = (x | type); + count = fwrite(&combined, sizeof(uint16_t), 1, output); + assert(count == 1); + + // 2B minutia position Y in pixels (upper 2b ignored, zeroed) + int16_t y = imageSizeY - minutia->position.y - 1; + assert(y <= 0x3fff); + count = fwrite(&y, sizeof(int16_t), 1, output); + assert(count == 1); + + // 1B direction, compatible with SourceAFIS angles + count = fwrite(&minutia->direction, sizeof(uint8_t), 1, output); + assert(count == 1); + + // 1B quality (ignored, zeroed) + count = fwrite(&oneByteRubbish, sizeof(oneByteRubbish), 1, output); + assert(count == 1); + } + + // 2B rubbish (extra data length, zeroed) + // N*1B rubbish (extra data) + count = fwrite(&twoByteRubbish, sizeof(twoByteRubbish), 1, output); + assert(count == 1); + + // Flush and close the binary file... + fflush(output); + fclose(output); +} + +void TemplateIO_ISO19794_2_2005_Import(const char *inputFileName, Template *template) +{ + // Open a binary file to output the template to... + FILE *input = fopen(inputFileName, "rb"); + + // ::TODO:: Decide if this is acceptable or not, it's what SourceAFIS does... + template->originalDpi = 500; + + // 4B magic "FMR\0" + char header[4]; + int count = fread(header, sizeof(uint8_t) * 4, 1, input); + assert(count == 1); + assert(strcmp("FMR", header) == 0); + + char version[4]; + count = fread(version, sizeof(version), 1, input); + assert(count == 1); + assert(strcmp(" 20", version) == 0); + + // 4B total length + int32_t totalLength; + count = fread(&totalLength, sizeof(int32_t), 1, input); + assert(count == 1); + + // 2B rubbish (zeroed) + int16_t twoByteRubbish; + count = fread(&twoByteRubbish, sizeof(twoByteRubbish), 1, input); + assert(count == 1); + + // 2B image size in pixels X + int16_t imageSizeX; + count = fread(&imageSizeX, sizeof(imageSizeX), 1, input); + assert(count == 1); + template->originalWidth = (imageSizeX * template-> originalDpi) / 500; + + // 2B image size in pixels Y + int16_t imageSizeY; + count = fread(&imageSizeY, sizeof(imageSizeY), 1, input); + assert(count == 1); + template->originalHeight = (imageSizeY * template->originalDpi) / 500; + + // 2B rubbish (pixels per cm X, set to 196 = 500dpi) + int16_t pixelsPerCm; + count = fread(&pixelsPerCm, sizeof(pixelsPerCm), 1, input); + assert(count == 1); + + // 2B rubbish (pixels per cm Y, set to 196 = 500dpi) + count = fread(&pixelsPerCm, sizeof(pixelsPerCm), 1, input); + assert(count == 1); + + // 1B rubbish (number of fingerprints, set to 1) + int8_t oneByteRubbish; + count = fread(&oneByteRubbish, sizeof(oneByteRubbish), 1, input); + assert(count == 1); + + // 1B rubbish (zeroed) + count = fread(&oneByteRubbish, sizeof(oneByteRubbish), 1, input); + assert(count == 1); + + // 1B rubbish (finger position, zeroed) + count = fread(&oneByteRubbish, sizeof(oneByteRubbish), 1, input); + assert(count == 1); + + // 1B rubbish (zeroed) + count = fread(&oneByteRubbish, sizeof(oneByteRubbish), 1, input); + assert(count == 1); + + // 1B rubbish (fingerprint quality, set to 100) + int8_t fingerprintQuality; + count = fread(&fingerprintQuality, sizeof(fingerprintQuality), 1, input); + assert(count == 1); + + // 1B minutia count + int8_t minutiaCount; + count = fread(&minutiaCount, sizeof(int8_t), 1, input); + assert(count == 1); + + // N*6B minutiae + for (int ii = 0; ii < minutiaCount; ++ii) + { + TemplateMinutia *templateMinutia = malloc(sizeof(TemplateMinutia)); + + // 2B minutia position X in pixels + // 2b (upper) minutia type (01 ending, 10 bifurcation, 00 other (considered ending)) + uint16_t combined, type; + int16_t x; + count = fread(&combined, sizeof(uint16_t), 1, input); + assert(count == 1); + + x = combined & 0x3FFF; + switch(combined & 0xc000) + { + case 0x4000: + type = ENDING; + break; + case 0x8000: + type = BIFURCATION; + break; + case 0: + type = OTHER; + break; + default: + assert("Incorrect minutia type"); + } + + // 2B minutia position Y in pixels (upper 2b ignored, zeroed) + int16_t y; + count = fread(&y, sizeof(int16_t), 1, input); + assert(count == 1); + y = template->originalHeight - 1 - (y & 0x3fff); + assert(y <= 0x3fff); + + // 1B direction, compatible with SourceAFIS angles + uint8_t direction; + count = fread(&direction, sizeof(uint8_t), 1, input); + assert(count == 1); + + // 1B quality (ignored, zeroed) + count = fread(&oneByteRubbish, sizeof(oneByteRubbish), 1, input); + assert(count == 1); + + // Add the minutia to the template list... + templateMinutia->position = (Point) {.x = x, .y = y}; + templateMinutia->direction = direction; + templateMinutia->type = type; + Template_AddMinutia(template, templateMinutia); + } + + // 2B rubbish (extra data length, zeroed) + // N*1B rubbish (extra data) + count = fread(&twoByteRubbish, sizeof(twoByteRubbish), 1, input); + assert(count == 1); + + // Close the binary file... + fclose(input); + + // Confirm total length... + assert(totalLength == (30 + (minutiaCount * 6))); +} diff --git a/Sources/Templates/TemplateIO.h b/Sources/Templates/TemplateIO.h new file mode 100644 index 0000000..cd198a4 --- /dev/null +++ b/Sources/Templates/TemplateIO.h @@ -0,0 +1,9 @@ +#ifndef LIBAFIS_TEMPLATEIO_H +#define LIBAFIS_TEMPLATEIO_H + +#include "Templates/Template.h" + +void TemplateIO_ISO19794_2_2005_Export(Template *, const char *); +void TemplateIO_ISO19794_2_2005_Import(const char *, Template *); + +#endif //LIBAFIS_TEMPLATEIO_H diff --git a/Sources/Tests/EndToEnd/Test_EndToEnd.c b/Sources/Tests/EndToEnd/Test_EndToEnd.c index c025144..175aa2e 100644 --- a/Sources/Tests/EndToEnd/Test_EndToEnd.c +++ b/Sources/Tests/EndToEnd/Test_EndToEnd.c @@ -59,7 +59,7 @@ static void ReadTemplate(const char *expectedFileName, Template *expectedTemplat ret = fread(&(minutia->type), sizeof(int32_t), 1, f); TEST_ASSERT_TRUE_MESSAGE(ret == 1, "ReadTemplate: failed on minutia->type"); - Template_AddMinuitia(expectedTemplate, minutia); + Template_AddMinutia(expectedTemplate, minutia); } /* Check end of file */ @@ -72,7 +72,6 @@ static void ReadTemplate(const char *expectedFileName, Template *expectedTemplat assert(ret != EOF); } - static void UnityFreeTemplate(Template *template) { while (List_GetCount(&template->minutiae) > 0) @@ -92,8 +91,8 @@ static void ImageToTemplate(const char *inputFileName, const char *expectedFileN struct perfdata perfdata; - Template template = Template_Constuct(); - Template expectedTemplate = Template_Constuct(); + Template template = Template_Construct(); + Template expectedTemplate = Template_Construct(); printf("%s %s\r\n", inputFileName, expectedFileName); @@ -134,7 +133,6 @@ static void ImageToTemplate(const char *inputFileName, const char *expectedFileN printf("Missing from expected = %d%%\n", missingFromExpected.count * 100 / expectedTemplate.minutiae.count); } - UnityFreeTemplate(&template); UnityFreeTemplate(&expectedTemplate); } diff --git a/Sources/Tests/Templates/TestRunner_Templates.c b/Sources/Tests/Templates/TestRunner_Templates.c new file mode 100644 index 0000000..c832f1d --- /dev/null +++ b/Sources/Tests/Templates/TestRunner_Templates.c @@ -0,0 +1,7 @@ +#include "unity.h" +#include "unity_fixture.h" + +TEST_GROUP_RUNNER(Templates) +{ + RUN_TEST_CASE(Templates, TemplateIO_ISO19794_2_2005); +} diff --git a/Sources/Tests/Templates/Test_Templates.c b/Sources/Tests/Templates/Test_Templates.c new file mode 100644 index 0000000..8357c57 --- /dev/null +++ b/Sources/Tests/Templates/Test_Templates.c @@ -0,0 +1,86 @@ +#include "General/List.h" +#include "Templates/Template.h" +#include "Templates/TemplateIO.h" +#include "unity.h" +#include "unity_fixture.h" + +#include +#include + +TEST_GROUP(Templates); + +TEST_SETUP(Templates) +{ +} + +TEST_TEAR_DOWN(Templates) +{ +} + +static const char *testFile = "test-output.bin"; + +static void TemplatesAreEqual(Template *expected, Template *actual) +{ + TEST_ASSERT_EQUAL_INT32_MESSAGE(expected->originalDpi, actual->originalDpi, "originalDpi's are different"); + TEST_ASSERT_EQUAL_INT32_MESSAGE(expected->originalWidth, actual->originalWidth, "originalWidth's are different"); + TEST_ASSERT_EQUAL_INT32_MESSAGE(expected->originalHeight, actual->originalHeight, "originalHeight's are different"); + + int32_t expectedMinutiaeCount = List_GetCount(&expected->minutiae); + TEST_ASSERT_EQUAL_INT32_MESSAGE(expectedMinutiaeCount, List_GetCount(&actual->minutiae), "minutia list lengths are different"); + + ListElement *expectedListElement = expected->minutiae.head; + ListElement *actualListElement = actual->minutiae.head; + while (expectedListElement != NULL && actualListElement != NULL) + { + TemplateMinutia *expectedMinutia = (TemplateMinutia *) expectedListElement->data; + TemplateMinutia *actualMinutia = (TemplateMinutia *) actualListElement->data; + + TEST_ASSERT_EQUAL_INT32_MESSAGE(expectedMinutia->position.x, actualMinutia->position.x, "minutia position.x's are different"); + TEST_ASSERT_EQUAL_INT32_MESSAGE(expectedMinutia->position.y, actualMinutia->position.y, "minutia position.x's are different"); + TEST_ASSERT_EQUAL_INT32_MESSAGE(expectedMinutia->direction, actualMinutia->direction, "minutia direction's are different"); + TEST_ASSERT_EQUAL_INT32_MESSAGE(expectedMinutia->type, actualMinutia->type, "minutia type's are different"); + + expectedListElement = expectedListElement->next; + actualListElement = actualListElement->next; + } +} + +static void PopulateTemplateToTestWith(Template *template) +{ + template->originalDpi = 500; + template->originalWidth = 100; + template->originalHeight = 100; + + for (int ii = 2; ii < 7; ii++) + { + TemplateMinutia *minutia = malloc(sizeof(TemplateMinutia)); + minutia->position = (Point) { .x = ii, .y = ii }; + minutia->direction = (uint8_t)ii*ii; + minutia->type = BIFURCATION; + Template_AddMinutia(template, minutia); + } +} +static void Template_UnityFree(Template *template) +{ + List_Destroy(&(template->minutiae), &(free)); +} + +TEST(Templates, TemplateIO_ISO19794_2_2005) +{ + // Create a template and export it to disk... + Template template = Template_Construct(); + PopulateTemplateToTestWith(&template); + TemplateIO_ISO19794_2_2005_Export(&template, testFile); + + // Create a template and read the previously exported template from disk... + Template fromDisk = Template_Construct(); + TemplateIO_ISO19794_2_2005_Import(testFile, &fromDisk); + + // They ahould be equal... + TemplatesAreEqual(&template, &fromDisk); + + // Clean up... + Template_UnityFree(&template); //With unity as the malloc happenned here + Template_Free(&fromDisk); + remove(testFile); +} diff --git a/Sources/Tests/all_tests.c b/Sources/Tests/all_tests.c index fd7207c..f3650ab 100644 --- a/Sources/Tests/all_tests.c +++ b/Sources/Tests/all_tests.c @@ -1,7 +1,7 @@ #ifdef _MSC_VER - #include + #include #else - #include + #include #endif #include "unity_fixture.h" @@ -60,6 +60,9 @@ static void RunAllTests(void) printf("\nBestMatchSkipper tests\n"); RUN_TEST_GROUP(BestMatchSkipper); + + printf("\nTemplate tests\n"); + RUN_TEST_GROUP(Templates); } int main(int argc, const char * argv[])