diff --git a/imageInput.c b/imageInput.c index d31a7da..0015137 100644 --- a/imageInput.c +++ b/imageInput.c @@ -1,29 +1,131 @@ +#include "imageInput.h" #include #include #include -#include "imageInput.h" -#define BUFFER_SIZE 100 #define FILE_HEADER_STRING "__info2_image_file_format__" -// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei -GrayScaleImage readImage() -{ - +/* ---------------------------------------------------------- + 1. Header prüfen + ---------------------------------------------------------- */ +static int readHeader(FILE *file) { + char header[sizeof(FILE_HEADER_STRING)]; + if (fread(header, 1, sizeof(FILE_HEADER_STRING) - 1, file) != + sizeof(FILE_HEADER_STRING) - 1) + return 0; + header[sizeof(FILE_HEADER_STRING) - 1] = '\0'; + return strcmp(header, FILE_HEADER_STRING) == 0; } -// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen -GrayScaleImageSeries *readImages(const char *path) -{ - GrayScaleImageSeries *series = NULL; - FILE *file = fopen("mnist_test.info2","rb"); - char headOfFile; - series = malloc(); - return series; +/* ---------------------------------------------------------- + 2. Meta-Daten lesen (unsigned short) + ---------------------------------------------------------- */ +static int readMeta(FILE *file, unsigned short *count, unsigned short *width, + unsigned short *height) { + if (fread(count, sizeof(unsigned short), 1, file) != 1) + return 0; + if (fread(width, sizeof(unsigned short), 1, file) != 1) + return 0; + if (fread(height, sizeof(unsigned short), 1, file) != 1) + return 0; + return 1; } -// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt -void clearSeries(GrayScaleImageSeries *series) -{ +/* ---------------------------------------------------------- + 3. Einzelbild lesen + ---------------------------------------------------------- */ +static int readSingleImage(FILE *file, GrayScaleImage *img, + unsigned short width, unsigned short height) { + img->width = width; + img->height = height; -} \ No newline at end of file + size_t numPixels = (size_t)width * (size_t)height; + img->buffer = malloc(numPixels); + if (!img->buffer) + return 0; + + if (fread(img->buffer, 1, numPixels, file) != numPixels) { + free(img->buffer); + img->buffer = NULL; + return 0; + } + return 1; +} + +/* ---------------------------------------------------------- + 4. Label lesen + ---------------------------------------------------------- */ +static int readLabel(FILE *file, unsigned char *label) { + return fread(label, 1, 1, file) == 1; +} + +/* ---------------------------------------------------------- + 5. Komplette Bildserie lesen + ---------------------------------------------------------- */ +GrayScaleImageSeries *readImages(const char *path) { + FILE *file = fopen(path, "rb"); + if (!file) + return NULL; + + if (!readHeader(file)) { + fclose(file); + return NULL; + } + + unsigned short count, width, height; + if (!readMeta(file, &count, &width, &height)) { + fclose(file); + return NULL; + } + + GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries)); + if (!series) { + fclose(file); + return NULL; + } + + series->count = count; + series->images = malloc(count * sizeof(GrayScaleImage)); + series->labels = malloc(count * sizeof(unsigned char)); + if (!series->images || !series->labels) { + free(series->images); + free(series->labels); + free(series); + fclose(file); + return NULL; + } + + for (unsigned int i = 0; i < count; i++) { + if (!readSingleImage(file, &series->images[i], width, height) || + !readLabel(file, &series->labels[i])) { + // Aufräumen bei Fehler + for (unsigned int j = 0; j < i; j++) { + free(series->images[j].buffer); + } + free(series->images); + free(series->labels); + free(series); + fclose(file); + return NULL; + } + } + + fclose(file); + return series; +} + +/* ---------------------------------------------------------- + 6. Speicher komplett freigeben + ---------------------------------------------------------- */ +void clearSeries(GrayScaleImageSeries *series) { + if (!series) + return; + + for (unsigned int i = 0; i < series->count; i++) { + free(series->images[i].buffer); + } + + free(series->images); + free(series->labels); + free(series); +} diff --git a/imageInputTests.c b/imageInputTests.c index 03240ab..e498ec4 100644 --- a/imageInputTests.c +++ b/imageInputTests.c @@ -1,143 +1,204 @@ - -#include -#include -#include -#include "unity.h" #include "imageInput.h" +#include "unity.h" +#include +#include +#include +/* --------------------------------------------------------- + Hilfsfunktion: Testdatei vorbereiten + --------------------------------------------------------- */ +static void prepareImageFile(const char *path, unsigned int width, + unsigned int height, unsigned int numberOfImages, + unsigned char label) { + FILE *file = fopen(path, "wb"); + if (!file) + return; -static void prepareImageFile(const char *path, unsigned short int width, unsigned short int height, unsigned int short numberOfImages, unsigned char label) -{ - FILE *file = fopen(path, "wb"); + // Header + const char *fileTag = "__info2_image_file_format__"; + fwrite(fileTag, 1, strlen(fileTag), file); - if(file != NULL) - { - const char *fileTag = "__info2_image_file_format__"; - GrayScalePixelType *zeroBuffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType)); + // Meta-Daten als unsigned short + unsigned short n = (unsigned short)numberOfImages; + unsigned short w = (unsigned short)width; + unsigned short h = (unsigned short)height; + fwrite(&n, sizeof(unsigned short), 1, file); + fwrite(&w, sizeof(unsigned short), 1, file); + fwrite(&h, sizeof(unsigned short), 1, file); - if(zeroBuffer != NULL) - { - fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file); - fwrite(&numberOfImages, sizeof(numberOfImages), 1, file); - fwrite(&width, sizeof(width), 1, file); - fwrite(&height, sizeof(height), 1, file); + // Pixelbuffer + GrayScalePixelType *buffer = + calloc(width * height, sizeof(GrayScalePixelType)); + if (!buffer) { + fclose(file); + return; + } + for (unsigned int i = 0; i < width * height; i++) + buffer[i] = (GrayScalePixelType)i; - for(int i = 0; i < numberOfImages; i++) - { - fwrite(zeroBuffer, sizeof(GrayScalePixelType), width * height, file); - fwrite(&label, sizeof(unsigned char), 1, file); - } + // Jedes Bild schreiben: Pixel + Label + for (unsigned int img = 0; img < numberOfImages; img++) { + fwrite(buffer, sizeof(GrayScalePixelType), width * height, file); + fwrite(&label, sizeof(unsigned char), 1, file); + } - free(zeroBuffer); - } - - fclose(file); - } + free(buffer); + fclose(file); } +/* --------------------------------------------------------- + Unit Tests + --------------------------------------------------------- */ -void test_readImagesReturnsCorrectNumberOfImages(void) -{ - GrayScaleImageSeries *series = NULL; - const unsigned short expectedNumberOfImages = 2; - const char *path = "testFile.info2"; - prepareImageFile(path, 8, 8, expectedNumberOfImages, 1); - series = readImages(path); - TEST_ASSERT_NOT_NULL(series); - TEST_ASSERT_EQUAL_UINT16(expectedNumberOfImages, series->count); - clearSeries(series); - remove(path); +void test_readImagesReturnsCorrectNumberOfImages(void) { + GrayScaleImageSeries *series = NULL; + const unsigned int expectedNumberOfImages = 2; + const char *path = "testFile.info2"; + prepareImageFile(path, 8, 8, expectedNumberOfImages, 1); + series = readImages(path); + TEST_ASSERT_NOT_NULL(series); + TEST_ASSERT_EQUAL_UINT(expectedNumberOfImages, series->count); + clearSeries(series); + remove(path); } -void test_readImagesReturnsCorrectImageWidth(void) -{ - GrayScaleImageSeries *series = NULL; - const unsigned short expectedWidth = 10; - const char *path = "testFile.info2"; - prepareImageFile(path, expectedWidth, 8, 2, 1); - series = readImages(path); - TEST_ASSERT_NOT_NULL(series); - TEST_ASSERT_NOT_NULL(series->images); - TEST_ASSERT_EQUAL_UINT16(2, series->count); - TEST_ASSERT_EQUAL_UINT16(expectedWidth, series->images[0].width); - TEST_ASSERT_EQUAL_UINT16(expectedWidth, series->images[1].width); - clearSeries(series); - remove(path); +void test_readImagesReturnsCorrectImageWidth(void) { + GrayScaleImageSeries *series = NULL; + const unsigned int expectedWidth = 10; + const char *path = "testFile.info2"; + prepareImageFile(path, expectedWidth, 8, 2, 1); + series = readImages(path); + TEST_ASSERT_NOT_NULL(series); + TEST_ASSERT_NOT_NULL(series->images); + TEST_ASSERT_EQUAL_UINT(2, series->count); + TEST_ASSERT_EQUAL_UINT(expectedWidth, series->images[0].width); + TEST_ASSERT_EQUAL_UINT(expectedWidth, series->images[1].width); + clearSeries(series); + remove(path); } -void test_readImagesReturnsCorrectImageHeight(void) -{ - GrayScaleImageSeries *series = NULL; - const unsigned short expectedHeight = 10; - const char *path = "testFile.info2"; - prepareImageFile(path, 8, expectedHeight, 2, 1); - series = readImages(path); - TEST_ASSERT_NOT_NULL(series); - TEST_ASSERT_NOT_NULL(series->images); - TEST_ASSERT_EQUAL_UINT16(2, series->count); - TEST_ASSERT_EQUAL_UINT16(expectedHeight, series->images[0].height); - TEST_ASSERT_EQUAL_UINT16(expectedHeight, series->images[1].height); - clearSeries(series); - remove(path); +void test_readImagesReturnsCorrectImageHeight(void) { + GrayScaleImageSeries *series = NULL; + const unsigned int expectedHeight = 10; + const char *path = "testFile.info2"; + prepareImageFile(path, 8, expectedHeight, 2, 1); + series = readImages(path); + TEST_ASSERT_NOT_NULL(series); + TEST_ASSERT_NOT_NULL(series->images); + TEST_ASSERT_EQUAL_UINT(2, series->count); + TEST_ASSERT_EQUAL_UINT(expectedHeight, series->images[0].height); + TEST_ASSERT_EQUAL_UINT(expectedHeight, series->images[1].height); + clearSeries(series); + remove(path); } -void test_readImagesReturnsCorrectLabels(void) -{ - const unsigned char expectedLabel = 15; +void test_readImagesReturnsCorrectLabels(void) { + const unsigned char expectedLabel = 15; - GrayScaleImageSeries *series = NULL; - const char *path = "testFile.info2"; - prepareImageFile(path, 8, 8, 2, expectedLabel); - series = readImages(path); - TEST_ASSERT_NOT_NULL(series); - TEST_ASSERT_NOT_NULL(series->labels); - TEST_ASSERT_EQUAL_UINT16(2, series->count); - for (int i = 0; i < 2; i++) { - TEST_ASSERT_EQUAL_UINT8(expectedLabel, series->labels[i]); - } - clearSeries(series); - remove(path); + GrayScaleImageSeries *series = NULL; + const char *path = "testFile.info2"; + prepareImageFile(path, 8, 8, 2, expectedLabel); + series = readImages(path); + TEST_ASSERT_NOT_NULL(series); + TEST_ASSERT_NOT_NULL(series->labels); + TEST_ASSERT_EQUAL_UINT(2, series->count); + for (int i = 0; i < 2; i++) { + TEST_ASSERT_EQUAL_UINT8(expectedLabel, series->labels[i]); + } + clearSeries(series); + remove(path); } -void test_readImagesReturnsNullOnNotExistingPath(void) -{ - const char *path = "testFile.txt"; - remove(path); +void test_readImagesReturnsNullOnNotExistingPath(void) { + const char *path = "testFile.txt"; + remove(path); + TEST_ASSERT_NULL(readImages(path)); +} + +void test_readImagesFailsOnWrongFileTag(void) { + const char *path = "testFile.info2"; + FILE *file = fopen(path, "w"); + if (file != NULL) { + fprintf(file, "some_tag "); + fclose(file); TEST_ASSERT_NULL(readImages(path)); + } + remove(path); } -void test_readImagesFailsOnWrongFileTag(void) -{ - const char *path = "testFile.info2"; - FILE *file = fopen(path, "w"); - if(file != NULL) - { - fprintf(file, "some_tag "); - fclose(file); - TEST_ASSERT_NULL(readImages(path)); - } - remove(path); +void test_read_GrayScale_Pixel(void) { + GrayScaleImageSeries *series = NULL; + const char *path = "testFile.info2"; + + prepareImageFile(path, 8, 8, 1, 1); + series = readImages(path); + + TEST_ASSERT_NOT_NULL(series); + TEST_ASSERT_NOT_NULL(series->images); + TEST_ASSERT_EQUAL_UINT(1, series->count); + + for (int i = 0; i < (8 * 8); i++) { + TEST_ASSERT_EQUAL_UINT8((GrayScalePixelType)i, series->images[0].buffer[i]); + } + + clearSeries(series); + remove(path); } -void setUp(void) { - // Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden +/* --------------------------------------------------------- + Optional: Mehrere Bilder gleichzeitig testen + --------------------------------------------------------- */ + +void test_readImagesMultipleImagesContent(void) { + GrayScaleImageSeries *series = NULL; + const char *path = "testFile.info2"; + const unsigned int numberOfImages = 3; + const unsigned int width = 4; + const unsigned int height = 4; + const unsigned char label = 7; + + prepareImageFile(path, width, height, numberOfImages, label); + + series = readImages(path); + TEST_ASSERT_NOT_NULL(series); + TEST_ASSERT_NOT_NULL(series->images); + TEST_ASSERT_NOT_NULL(series->labels); + TEST_ASSERT_EQUAL_UINT(numberOfImages, series->count); + + for (unsigned int img = 0; img < numberOfImages; img++) { + for (unsigned int i = 0; i < width * height; i++) + TEST_ASSERT_EQUAL_UINT8((GrayScalePixelType)i, + series->images[img].buffer[i]); + TEST_ASSERT_EQUAL_UINT8(label, series->labels[img]); + } + + clearSeries(series); + remove(path); } -void tearDown(void) { - // Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden +/* --------------------------------------------------------- + Setup / Teardown + --------------------------------------------------------- */ +void setUp(void) {} +void tearDown(void) {} + +/* --------------------------------------------------------- + main() + --------------------------------------------------------- */ +int main(void) { + UNITY_BEGIN(); + + printf("\n============================\nImage input " + "tests\n============================\n"); + + RUN_TEST(test_readImagesReturnsCorrectNumberOfImages); + RUN_TEST(test_readImagesReturnsCorrectImageWidth); + RUN_TEST(test_readImagesReturnsCorrectImageHeight); + RUN_TEST(test_readImagesReturnsCorrectLabels); + RUN_TEST(test_readImagesReturnsNullOnNotExistingPath); + RUN_TEST(test_readImagesFailsOnWrongFileTag); + RUN_TEST(test_read_GrayScale_Pixel); + RUN_TEST(test_readImagesMultipleImagesContent); + + return UNITY_END(); } - -int main() -{ - UNITY_BEGIN(); - - printf("\n============================\nImage input tests\n============================\n"); - RUN_TEST(test_readImagesReturnsCorrectNumberOfImages); - RUN_TEST(test_readImagesReturnsCorrectImageWidth); - RUN_TEST(test_readImagesReturnsCorrectImageHeight); - RUN_TEST(test_readImagesReturnsCorrectLabels); - RUN_TEST(test_readImagesReturnsNullOnNotExistingPath); - RUN_TEST(test_readImagesFailsOnWrongFileTag); - - return UNITY_END(); -} \ No newline at end of file diff --git a/matrix.c b/matrix.c index 4a2ec16..b20d718 100644 --- a/matrix.c +++ b/matrix.c @@ -10,31 +10,45 @@ MatrixType *buffer; //Zeiger auf Speicherbereich Reihen*Spalten } Matrix;*/ Matrix createMatrix(unsigned int rows, unsigned int cols) { - + Matrix matrix; Matrix errorMatrix = {0, 0, NULL}; if (rows == 0 || cols == 0) { return errorMatrix; } - MatrixType *buffer = - malloc(rows * cols * sizeof(MatrixType)); // Speicher reservieren, malloc - // liefert Zeiger auf Speicher - Matrix newMatrix = {rows, cols, buffer}; // neue Matrix nach struct - return newMatrix; + matrix.rows = rows; + matrix.cols = cols; + + matrix.buffer = malloc(rows * cols * sizeof(MatrixType)); + if (matrix.buffer == NULL) { + matrix.rows = 0; + matrix.cols = 0; + return matrix; + } + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + matrix.buffer[i * matrix.cols + j] = UNDEFINED_MATRIX_VALUE; + } + } + return matrix; } void clearMatrix(Matrix *matrix) { - matrix->buffer = UNDEFINED_MATRIX_VALUE; - matrix->rows = UNDEFINED_MATRIX_VALUE; - matrix->cols = UNDEFINED_MATRIX_VALUE; - free((*matrix).buffer); // Speicher freigeben + + if (matrix->buffer != NULL) { + free((*matrix).buffer); + matrix->buffer = NULL; + } + matrix->rows = 0; + matrix->cols = 0; } void setMatrixAt(const MatrixType value, Matrix matrix, const unsigned int rowIdx, // Kopie der Matrix wird übergeben const unsigned int colIdx) { - if (rowIdx >= matrix.rows || - colIdx >= matrix.cols) { // Speichergröße nicht überschreiten + if (rowIdx >= matrix.rows || colIdx >= matrix.cols || + matrix.buffer == NULL) { // Speichergröße nicht überschreiten return; } @@ -45,9 +59,9 @@ void setMatrixAt(const MatrixType value, Matrix matrix, MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, // Kopie der Matrix wird übergeben unsigned int colIdx) { - if (rowIdx >= matrix.rows || - colIdx >= matrix.cols) { // Speichergröße nicht überschreiten - return 0; + if (rowIdx >= matrix.rows || colIdx >= matrix.cols || + matrix.buffer == NULL) { // Speichergröße nicht überschreiten + return UNDEFINED_MATRIX_VALUE; } MatrixType value = matrix.buffer[rowIdx * matrix.cols + colIdx]; @@ -103,6 +117,9 @@ Matrix add(const Matrix matrix1, const Matrix matrix2) { { Matrix result = createMatrix(rows1, cols1); // Speicher reservieren + if (result.buffer == NULL) { + return (Matrix){0, 0, NULL}; + } for (int i = 0; i < (rows1 * cols1); i++) { // addieren @@ -120,16 +137,22 @@ Matrix add(const Matrix matrix1, const Matrix matrix2) { if (cols1 == 1) { Matrix result = createMatrix(rows2, cols2); + if (result.buffer == NULL) { + return (Matrix){0, 0, NULL}; + } Matrix copy1 = broadCastCols(matrix1, rows2, cols2); - - for (int i = 0; i < (rows2 * cols2); i++) { // addieren - - result.buffer[i] = - (copy1.buffer[i] + - matrix2.buffer[i]); // buffer[i] ⇔ *(buffer + i) Adresse = - // Startadresse + (i * sizeof(MatrixType)) + if (!copy1.buffer) { + clearMatrix(&result); + return (Matrix){0, 0, NULL}; } + + for (unsigned int i = 0; i < rows2 * cols2; i++) { + result.buffer[i] = copy1.buffer[i] + matrix2.buffer[i]; + } + + /* freigeben, weil nicht mehr benötigt */ + clearMatrix(©1); return result; // add und return @@ -137,19 +160,22 @@ Matrix add(const Matrix matrix1, const Matrix matrix2) { } else if (cols2 == 1) { Matrix result = createMatrix(rows1, cols1); + if (result.buffer == NULL) { + Matrix error = {0, 0, NULL}; + return error; + } + // Matrix2 hat nur eine Spalte -> horizontal broadcasten Matrix copy2 = broadCastCols(matrix2, rows1, cols1); - for (int i = 0; i < (rows1 * cols1); i++) { // addieren - - result.buffer[i] = - (matrix1.buffer[i] + - copy2.buffer[i]); // buffer[i] ⇔ *(buffer + i) Adresse = - // Startadresse + (i * sizeof(MatrixType)) + for (unsigned int i = 0; i < rows1 * cols1; i++) { + result.buffer[i] = matrix1.buffer[i] + copy2.buffer[i]; } - return result; - // add und return + // Optional: Speicher von copy2 freigeben + clearMatrix(©2); + + return result; } else { @@ -167,6 +193,9 @@ Matrix add(const Matrix matrix1, const Matrix matrix2) { if (rows1 == 1) { Matrix result = createMatrix(rows2, cols2); + if (result.buffer == NULL) { + return (Matrix){0, 0, NULL}; + } Matrix copy1 = broadCastRows(matrix1, rows2, cols2); @@ -184,8 +213,12 @@ Matrix add(const Matrix matrix1, const Matrix matrix2) { } else if (rows2 == 1) { Matrix result = createMatrix(rows1, cols1); + if (result.buffer == NULL) { + return (Matrix){0, 0, NULL}; + } + + Matrix copy2 = broadCastRows(matrix2, rows1, cols1); - Matrix copy2 = broadCastCols(matrix2, rows1, cols1); // add und return for (int i = 0; i < (rows1 * cols1); i++) { // addieren @@ -242,4 +275,4 @@ Matrix multiply(const Matrix matrix1, const Matrix matrix2) { Matrix errorMatrix = {0, 0, NULL}; return errorMatrix; } -} \ No newline at end of file +}