From 4db000de05d898cdd142876d6872accf37a3658e Mon Sep 17 00:00:00 2001 From: ehrnspergersi95041 Date: Sun, 16 Nov 2025 20:16:51 +0100 Subject: [PATCH] neuralNetworkTests.c angepasst und code ausgetauscht, alter Code ist in neuralnetworktestsold.c Initialisierung von imageInput.c und Troubleshooting bei den Unity Tests --- Start_Windows/imageInput.c | 181 +++++++++++++++- Start_Windows/matrix.c | 2 +- Start_Windows/neuralNetworkTests.c | 72 +++---- Start_Windows/neuralnetworktestsold.c | 300 ++++++++++++++++++++++++++ 4 files changed, 506 insertions(+), 49 deletions(-) create mode 100644 Start_Windows/neuralnetworktestsold.c diff --git a/Start_Windows/imageInput.c b/Start_Windows/imageInput.c index bb30de1..9322c99 100644 --- a/Start_Windows/imageInput.c +++ b/Start_Windows/imageInput.c @@ -1,22 +1,183 @@ +// #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 +// +// // TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen +// GrayScaleImageSeries *readImages(const char *path) +// { +// GrayScaleImageSeries *series = NULL; +// +// return series; +// } +// +// // TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt +// void clearSeries(GrayScaleImageSeries *series) +// { +// +// } + #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 +// --------------------------------------------------------- +// Hilfsfunktionen (static, nur in diesem Modul sichtbar) +// --------------------------------------------------------- + +/** + * Prüft, ob die Datei mit dem korrekten Header-String beginnt. + * Gibt 1 zurück, wenn korrekt, sonst 0. + */ +static int checkFileHeader(FILE *file) { + const char *expectedHeader = FILE_HEADER_STRING; + size_t headerLen = strlen(expectedHeader); + char buffer[100]; // Puffer groß genug für den Header + + // Wir lesen genau so viele Bytes, wie der Header lang ist + if (fread(buffer, sizeof(char), headerLen, file) != headerLen) { + return 0; // Lesefehler oder Datei zu kurz + } + + // Null-Terminierung sicherstellen für strcmp (obwohl wir auch memcmp nutzen könnten) + buffer[headerLen] = '\0'; + + if (strcmp(buffer, expectedHeader) != 0) { + return 0; // Header stimmt nicht überein + } + + return 1; +} + +/** + * Reserviert den Speicher für die Basis-Struktur der Bildserie. + * Reserviert Arrays für 'images' und 'labels', aber noch nicht die Pixel-Buffer der einzelnen Bilder. + */ +static GrayScaleImageSeries *allocateSeries(unsigned short count) { + GrayScaleImageSeries *series = (GrayScaleImageSeries *)malloc(sizeof(GrayScaleImageSeries)); + if (series == NULL) return NULL; + + series->count = count; + + // Speicher für das Array der Bild-Strukturen + series->images = (GrayScaleImage *)calloc(count, sizeof(GrayScaleImage)); + // Speicher für das Array der Labels + series->labels = (unsigned char *)calloc(count, sizeof(unsigned char)); + + if (series->images == NULL || series->labels == NULL) { + // Falls eine Allokation fehlschlägt, alles bisherige freigeben + free(series->images); + free(series->labels); + free(series); + return NULL; + } -// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen -GrayScaleImageSeries *readImages(const char *path) -{ - GrayScaleImageSeries *series = NULL; - return series; } -// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt -void clearSeries(GrayScaleImageSeries *series) -{ +/** + * Liest ein einzelnes Bild (Pixeldaten) und das zugehörige Label. + */ +static int readSingleImage(FILE *file, GrayScaleImage *image, unsigned char *label, unsigned short width, unsigned short height) { + image->width = width; + image->height = height; + + // Speicher für die Pixelwerte reservieren + image->buffer = (GrayScalePixelType *)malloc(width * height * sizeof(GrayScalePixelType)); + if (image->buffer == NULL) { + return 0; + } + + // Pixelwerte lesen + if (fread(image->buffer, sizeof(GrayScalePixelType), width * height, file) != width * height) { + return 0; + } + + // Label lesen + if (fread(label, sizeof(unsigned char), 1, file) != 1) { + return 0; + } + + return 1; +} + +// --------------------------------------------------------- +// Hauptfunktionen (öffentliche API) +// --------------------------------------------------------- + +GrayScaleImageSeries *readImages(const char *path) { + FILE *file = fopen(path, "rb"); // WICHTIG: "rb" für binary mode + if (file == NULL) { + return NULL; + } + + // 1. Header prüfen + if (!checkFileHeader(file)) { + fclose(file); + return NULL; + } + + // 2. Dimensionen lesen (Anzahl, Breite, Höhe) + unsigned short count, width, height; + int readCount = 0; + readCount += fread(&count, sizeof(unsigned short), 1, file); + readCount += fread(&width, sizeof(unsigned short), 1, file); + readCount += fread(&height, sizeof(unsigned short), 1, file); + + if (readCount != 3) { + fclose(file); + return NULL; + } + + // 3. Speicherstruktur vorbereiten + GrayScaleImageSeries *series = allocateSeries(count); + if (series == NULL) { + fclose(file); + return NULL; + } + + // 4. Jedes Bild einzeln einlesen + for (int i = 0; i < count; i++) { + if (!readSingleImage(file, &series->images[i], &series->labels[i], width, height)) { + // Fehler beim Lesen eines Bildes -> Aufräumen + clearSeries(series); + fclose(file); + return NULL; + } + } + + fclose(file); + return series; +} + +void clearSeries(GrayScaleImageSeries *series) { + if (series == NULL) return; + + // 1. Pixel-Buffer jedes einzelnen Bildes freigeben + if (series->images != NULL) { + for (int i = 0; i < series->count; i++) { + if (series->images[i].buffer != NULL) { + free(series->images[i].buffer); + series->images[i].buffer = NULL; + } + } + // 2. Das Array der Bild-Strukturen freigeben + free(series->images); + } + + // 3. Das Label-Array freigeben + if (series->labels != NULL) { + free(series->labels); + } + + // 4. Die Hauptstruktur freigeben + free(series); } \ No newline at end of file diff --git a/Start_Windows/matrix.c b/Start_Windows/matrix.c index 5390c55..8dd81b4 100644 --- a/Start_Windows/matrix.c +++ b/Start_Windows/matrix.c @@ -116,7 +116,7 @@ Matrix add(const Matrix matrix1, const Matrix matrix2) for(unsigned int i = 0; i < resRows; ++i) { - for(unsigned int j = 0; i < resCols; ++j) + for(unsigned int j = 0; j < resCols; ++j) { unsigned int i1 = (matrix1.rows == 1) ? 0 : i; unsigned int j1 = (matrix1.cols == 1) ? 0 : j; diff --git a/Start_Windows/neuralNetworkTests.c b/Start_Windows/neuralNetworkTests.c index 37b048d..4ab77d1 100644 --- a/Start_Windows/neuralNetworkTests.c +++ b/Start_Windows/neuralNetworkTests.c @@ -5,24 +5,7 @@ #include "./unity/unity.h" #include "neuralNetwork.h" - // TODO -// Das Neuronale Netz -// Inhalte: Strukturen, Dateien schreiben -// Ein neuronales besteht aus verschiedenen Schichten und ihren Parametern. -// Die Struktur NeuralNetwork und die Implementierung in der entsprechenden Quelltextdatei bildet dies ab. -// Für die passenden Unittests fehlt jedoch noch eine Methode, die eine gültige Testdatei erzeugt, -// mit der die Funktionalität getestet werden kann. -// Die Datei beginnt mit dem Identifikationstag __info2_neural_network_file_format__, -// gefolgt von den einzelnen Schichten. -// -// Aufgaben: -// 1) Implementieren Sie die Funktion prepareNeuralNetworkFile() in neuralNetworkTests.c. -// Praktikum Informatik 2 -// -// Wintersemester 2025 -// 2) Stellen Sie sicher, dass alle Unittests erfolgreich durchlaufen. -// make neuralNetworkTests && ./runNeuralNetworkTests - +// --- Implementierung der Hilfsfunktion --- static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) { @@ -31,44 +14,46 @@ static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) return; } - // 1. Header schreiben + // 1. Header schreiben (ohne Nullterminator) const char *fileHeader = "__info2_neural_network_file_format__"; fwrite(fileHeader, sizeof(char), strlen(fileHeader), file); - // Prüfen ob Layer existieren if (nn.numberOfLayers > 0) { - // 2. Die Input-Dimension der ALLERERSTEN Schicht schreiben - // (Das sind die Spalten der Gewichtsmatrix der ersten Schicht) + // 2. Input-Dimension der ersten Schicht schreiben (Spalten der ersten Gewichtsmatrix) int inputDim = nn.layers[0].weights.cols; fwrite(&inputDim, sizeof(int), 1, file); // 3. Durch alle Schichten iterieren for(int i = 0; i < nn.numberOfLayers; i++) { - // Die Output-Dimension dieser Schicht schreiben (Zeilen der Gewichtsmatrix) + // Output-Dimension der aktuellen Schicht schreiben (Zeilen der Gewichtsmatrix) int outputDim = nn.layers[i].weights.rows; fwrite(&outputDim, sizeof(int), 1, file); - // 4. Gewichte (Weights) schreiben (nur den Buffer, keine Dimensionen mehr!) - // loadModel weiß durch inputDim und outputDim schon, wie groß die Matrix ist. + // 4. Gewichte schreiben int weightsCount = nn.layers[i].weights.rows * nn.layers[i].weights.cols; - fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType), weightsCount, file); + if (nn.layers[i].weights.buffer != NULL) { + fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType), weightsCount, file); + } - // 5. Biases schreiben (nur den Buffer) + // 5. Biases schreiben int biasCount = nn.layers[i].biases.rows * nn.layers[i].biases.cols; - fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType), biasCount, file); + if (nn.layers[i].biases.buffer != NULL) { + fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType), biasCount, file); + } } } - // 6. Eine 0 schreiben, um das Ende der Dimensionen zu signalisieren - // (loadModel bricht die while-Schleife ab, wenn readDimension 0 liefert) + // 6. Abbruchsignal (Dimension 0) schreiben int stopMark = 0; fwrite(&stopMark, sizeof(int), 1, file); fclose(file); } +// --- Unit Tests --- + void test_loadModelReturnsCorrectNumberOfLayers(void) { const char *path = "some__nn_test_file.info2"; @@ -199,9 +184,7 @@ void test_loadModelFailsOnWrongFileTag(void) if(file != NULL) { const char *fileTag = "info2_neural_network_file_format"; - fwrite(fileTag, sizeof(char), strlen(fileTag), file); - fclose(file); } @@ -239,6 +222,8 @@ void test_clearModelSetsMembersToNull(void) static void someActivation(Matrix *matrix) { + if (matrix == NULL || matrix->buffer == NULL) return; + for(int i = 0; i < matrix->rows * matrix->cols; i++) { matrix->buffer[i] = fabs(matrix->buffer[i]); @@ -251,26 +236,37 @@ void test_predictReturnsCorrectLabels(void) GrayScalePixelType imageBuffer1[] = {10, 30, 25, 17}; GrayScalePixelType imageBuffer2[] = {20, 40, 10, 128}; GrayScaleImage inputImages[] = {{.buffer=imageBuffer1, .width=2, .height=2}, {.buffer=imageBuffer2, .width=2, .height=2}}; - MatrixType weightsBuffer1[] = {1, -2, 3, -4, 5, -6, 7, -8}; - MatrixType weightsBuffer2[] = {-9, 10, 11, 12, 13, 14}; - MatrixType weightsBuffer3[] = {-15, 16, 17, 18, -19, 20, 21, 22, 23, -24, 25, 26, 27, -28, -29}; + + // Wir nutzen explizite Casts auf MatrixType, um sicherzustellen, dass die Typen stimmen + // (besonders wichtig, falls MatrixType double ist, aber hier Ints stehen) + MatrixType weightsBuffer1[] = {(MatrixType)1, (MatrixType)-2, (MatrixType)3, (MatrixType)-4, (MatrixType)5, (MatrixType)-6, (MatrixType)7, (MatrixType)-8}; + MatrixType weightsBuffer2[] = {(MatrixType)-9, (MatrixType)10, (MatrixType)11, (MatrixType)12, (MatrixType)13, (MatrixType)14}; + MatrixType weightsBuffer3[] = {(MatrixType)-15, (MatrixType)16, (MatrixType)17, (MatrixType)18, (MatrixType)-19, (MatrixType)20, (MatrixType)21, (MatrixType)22, (MatrixType)23, (MatrixType)-24, (MatrixType)25, (MatrixType)26, (MatrixType)27, (MatrixType)-28, (MatrixType)-29}; + Matrix weights1 = {.buffer=weightsBuffer1, .rows=2, .cols=4}; Matrix weights2 = {.buffer=weightsBuffer2, .rows=3, .cols=2}; Matrix weights3 = {.buffer=weightsBuffer3, .rows=5, .cols=3}; - MatrixType biasBuffer1[] = {200, 0}; - MatrixType biasBuffer2[] = {0, -100, 0}; - MatrixType biasBuffer3[] = {0, -1000, 0, 2000, 0}; + + MatrixType biasBuffer1[] = {(MatrixType)200, (MatrixType)0}; + MatrixType biasBuffer2[] = {(MatrixType)0, (MatrixType)-100, (MatrixType)0}; + MatrixType biasBuffer3[] = {(MatrixType)0, (MatrixType)-1000, (MatrixType)0, (MatrixType)2000, (MatrixType)0}; + Matrix biases1 = {.buffer=biasBuffer1, .rows=2, .cols=1}; Matrix biases2 = {.buffer=biasBuffer2, .rows=3, .cols=1}; Matrix biases3 = {.buffer=biasBuffer3, .rows=5, .cols=1}; + Layer layers[] = {{.weights=weights1, .biases=biases1, .activation=someActivation}, \ {.weights=weights2, .biases=biases2, .activation=someActivation}, \ {.weights=weights3, .biases=biases3, .activation=someActivation}}; + NeuralNetwork netUnderTest = {.layers=layers, .numberOfLayers=3}; + unsigned char *predictedLabels = predict(netUnderTest, inputImages, 2); + TEST_ASSERT_NOT_NULL(predictedLabels); int n = (int)(sizeof(expectedLabels) / sizeof(expectedLabels[0])); TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedLabels, predictedLabels, n); + free(predictedLabels); } diff --git a/Start_Windows/neuralnetworktestsold.c b/Start_Windows/neuralnetworktestsold.c new file mode 100644 index 0000000..37b048d --- /dev/null +++ b/Start_Windows/neuralnetworktestsold.c @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include "./unity/unity.h" +#include "neuralNetwork.h" + + // TODO +// Das Neuronale Netz +// Inhalte: Strukturen, Dateien schreiben +// Ein neuronales besteht aus verschiedenen Schichten und ihren Parametern. +// Die Struktur NeuralNetwork und die Implementierung in der entsprechenden Quelltextdatei bildet dies ab. +// Für die passenden Unittests fehlt jedoch noch eine Methode, die eine gültige Testdatei erzeugt, +// mit der die Funktionalität getestet werden kann. +// Die Datei beginnt mit dem Identifikationstag __info2_neural_network_file_format__, +// gefolgt von den einzelnen Schichten. +// +// Aufgaben: +// 1) Implementieren Sie die Funktion prepareNeuralNetworkFile() in neuralNetworkTests.c. +// Praktikum Informatik 2 +// +// Wintersemester 2025 +// 2) Stellen Sie sicher, dass alle Unittests erfolgreich durchlaufen. +// make neuralNetworkTests && ./runNeuralNetworkTests + + +static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn) +{ + FILE *file = fopen(path, "wb"); + if (file == NULL) { + return; + } + + // 1. Header schreiben + const char *fileHeader = "__info2_neural_network_file_format__"; + fwrite(fileHeader, sizeof(char), strlen(fileHeader), file); + + // Prüfen ob Layer existieren + if (nn.numberOfLayers > 0) + { + // 2. Die Input-Dimension der ALLERERSTEN Schicht schreiben + // (Das sind die Spalten der Gewichtsmatrix der ersten Schicht) + int inputDim = nn.layers[0].weights.cols; + fwrite(&inputDim, sizeof(int), 1, file); + + // 3. Durch alle Schichten iterieren + for(int i = 0; i < nn.numberOfLayers; i++) + { + // Die Output-Dimension dieser Schicht schreiben (Zeilen der Gewichtsmatrix) + int outputDim = nn.layers[i].weights.rows; + fwrite(&outputDim, sizeof(int), 1, file); + + // 4. Gewichte (Weights) schreiben (nur den Buffer, keine Dimensionen mehr!) + // loadModel weiß durch inputDim und outputDim schon, wie groß die Matrix ist. + int weightsCount = nn.layers[i].weights.rows * nn.layers[i].weights.cols; + fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType), weightsCount, file); + + // 5. Biases schreiben (nur den Buffer) + int biasCount = nn.layers[i].biases.rows * nn.layers[i].biases.cols; + fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType), biasCount, file); + } + } + + // 6. Eine 0 schreiben, um das Ende der Dimensionen zu signalisieren + // (loadModel bricht die while-Schleife ab, wenn readDimension 0 liefert) + int stopMark = 0; + fwrite(&stopMark, sizeof(int), 1, file); + + fclose(file); +} + +void test_loadModelReturnsCorrectNumberOfLayers(void) +{ + const char *path = "some__nn_test_file.info2"; + MatrixType buffer1[] = {1, 2, 3, 4, 5, 6}; + MatrixType buffer2[] = {1, 2, 3, 4, 5, 6}; + Matrix weights1 = {.buffer=buffer1, .rows=3, .cols=2}; + Matrix weights2 = {.buffer=buffer2, .rows=2, .cols=3}; + MatrixType buffer3[] = {1, 2, 3}; + MatrixType buffer4[] = {1, 2}; + Matrix biases1 = {.buffer=buffer3, .rows=3, .cols=1}; + Matrix biases2 = {.buffer=buffer4, .rows=2, .cols=1}; + Layer layers[] = {{.weights=weights1, .biases=biases1}, {.weights=weights2, .biases=biases2}}; + + NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=2}; + NeuralNetwork netUnderTest; + + prepareNeuralNetworkFile(path, expectedNet); + + netUnderTest = loadModel(path); + remove(path); + + TEST_ASSERT_EQUAL_INT(expectedNet.numberOfLayers, netUnderTest.numberOfLayers); + clearModel(&netUnderTest); +} + +void test_loadModelReturnsCorrectWeightDimensions(void) +{ + const char *path = "some__nn_test_file.info2"; + MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; + Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; + MatrixType biasBuffer[] = {7, 8, 9}; + Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; + Layer layers[] = {{.weights=weights, .biases=biases}}; + + NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; + NeuralNetwork netUnderTest; + + prepareNeuralNetworkFile(path, expectedNet); + + netUnderTest = loadModel(path); + remove(path); + + TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols); + clearModel(&netUnderTest); +} + +void test_loadModelReturnsCorrectBiasDimensions(void) +{ + const char *path = "some__nn_test_file.info2"; + MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; + Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; + MatrixType biasBuffer[] = {7, 8, 9}; + Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; + Layer layers[] = {{.weights=weights, .biases=biases}}; + + NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; + NeuralNetwork netUnderTest; + + prepareNeuralNetworkFile(path, expectedNet); + + netUnderTest = loadModel(path); + remove(path); + + TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.rows, netUnderTest.layers[0].biases.rows); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].biases.cols, netUnderTest.layers[0].biases.cols); + clearModel(&netUnderTest); +} + +void test_loadModelReturnsCorrectWeights(void) +{ + const char *path = "some__nn_test_file.info2"; + MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; + Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; + MatrixType biasBuffer[] = {7, 8, 9}; + Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; + Layer layers[] = {{.weights=weights, .biases=biases}}; + + NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; + NeuralNetwork netUnderTest; + + prepareNeuralNetworkFile(path, expectedNet); + + netUnderTest = loadModel(path); + remove(path); + + TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols); + int n = netUnderTest.layers[0].weights.rows * netUnderTest.layers[0].weights.cols; + TEST_ASSERT_EQUAL_INT_ARRAY(expectedNet.layers[0].weights.buffer, netUnderTest.layers[0].weights.buffer, n); + clearModel(&netUnderTest); +} + +void test_loadModelReturnsCorrectBiases(void) +{ + const char *path = "some__nn_test_file.info2"; + MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; + Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; + MatrixType biasBuffer[] = {7, 8, 9}; + Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; + Layer layers[] = {{.weights=weights, .biases=biases}}; + + NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; + NeuralNetwork netUnderTest; + + prepareNeuralNetworkFile(path, expectedNet); + + netUnderTest = loadModel(path); + remove(path); + + TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.rows, netUnderTest.layers[0].weights.rows); + TEST_ASSERT_EQUAL_INT(expectedNet.layers[0].weights.cols, netUnderTest.layers[0].weights.cols); + int n = netUnderTest.layers[0].biases.rows * netUnderTest.layers[0].biases.cols; + TEST_ASSERT_EQUAL_INT_ARRAY(expectedNet.layers[0].biases.buffer, netUnderTest.layers[0].biases.buffer, n); + clearModel(&netUnderTest); +} + +void test_loadModelFailsOnWrongFileTag(void) +{ + const char *path = "some_nn_test_file.info2"; + NeuralNetwork netUnderTest; + FILE *file = fopen(path, "wb"); + + if(file != NULL) + { + const char *fileTag = "info2_neural_network_file_format"; + + fwrite(fileTag, sizeof(char), strlen(fileTag), file); + + fclose(file); + } + + netUnderTest = loadModel(path); + + remove(path); + + TEST_ASSERT_NULL(netUnderTest.layers); + TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers); +} + +void test_clearModelSetsMembersToNull(void) +{ + const char *path = "some__nn_test_file.info2"; + MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6}; + Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2}; + MatrixType biasBuffer[] = {7, 8, 9}; + Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1}; + Layer layers[] = {{.weights=weights, .biases=biases}}; + + NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1}; + NeuralNetwork netUnderTest; + + prepareNeuralNetworkFile(path, expectedNet); + + netUnderTest = loadModel(path); + remove(path); + + TEST_ASSERT_NOT_NULL(netUnderTest.layers); + TEST_ASSERT_TRUE(netUnderTest.numberOfLayers > 0); + clearModel(&netUnderTest); + TEST_ASSERT_NULL(netUnderTest.layers); + TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers); +} + +static void someActivation(Matrix *matrix) +{ + for(int i = 0; i < matrix->rows * matrix->cols; i++) + { + matrix->buffer[i] = fabs(matrix->buffer[i]); + } +} + +void test_predictReturnsCorrectLabels(void) +{ + const unsigned char expectedLabels[] = {4, 2}; + GrayScalePixelType imageBuffer1[] = {10, 30, 25, 17}; + GrayScalePixelType imageBuffer2[] = {20, 40, 10, 128}; + GrayScaleImage inputImages[] = {{.buffer=imageBuffer1, .width=2, .height=2}, {.buffer=imageBuffer2, .width=2, .height=2}}; + MatrixType weightsBuffer1[] = {1, -2, 3, -4, 5, -6, 7, -8}; + MatrixType weightsBuffer2[] = {-9, 10, 11, 12, 13, 14}; + MatrixType weightsBuffer3[] = {-15, 16, 17, 18, -19, 20, 21, 22, 23, -24, 25, 26, 27, -28, -29}; + Matrix weights1 = {.buffer=weightsBuffer1, .rows=2, .cols=4}; + Matrix weights2 = {.buffer=weightsBuffer2, .rows=3, .cols=2}; + Matrix weights3 = {.buffer=weightsBuffer3, .rows=5, .cols=3}; + MatrixType biasBuffer1[] = {200, 0}; + MatrixType biasBuffer2[] = {0, -100, 0}; + MatrixType biasBuffer3[] = {0, -1000, 0, 2000, 0}; + Matrix biases1 = {.buffer=biasBuffer1, .rows=2, .cols=1}; + Matrix biases2 = {.buffer=biasBuffer2, .rows=3, .cols=1}; + Matrix biases3 = {.buffer=biasBuffer3, .rows=5, .cols=1}; + Layer layers[] = {{.weights=weights1, .biases=biases1, .activation=someActivation}, \ + {.weights=weights2, .biases=biases2, .activation=someActivation}, \ + {.weights=weights3, .biases=biases3, .activation=someActivation}}; + NeuralNetwork netUnderTest = {.layers=layers, .numberOfLayers=3}; + unsigned char *predictedLabels = predict(netUnderTest, inputImages, 2); + TEST_ASSERT_NOT_NULL(predictedLabels); + int n = (int)(sizeof(expectedLabels) / sizeof(expectedLabels[0])); + TEST_ASSERT_EQUAL_UINT8_ARRAY(expectedLabels, predictedLabels, n); + free(predictedLabels); +} + +void setUp(void) { + // Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden +} + +void tearDown(void) { + // Hier kann Bereinigungsarbeit nach jedem Test durchgeführt werden +} + +int main() +{ + UNITY_BEGIN(); + + printf("\n============================\nNeural network tests\n============================\n"); + RUN_TEST(test_loadModelReturnsCorrectNumberOfLayers); + RUN_TEST(test_loadModelReturnsCorrectWeightDimensions); + RUN_TEST(test_loadModelReturnsCorrectBiasDimensions); + RUN_TEST(test_loadModelReturnsCorrectWeights); + RUN_TEST(test_loadModelReturnsCorrectBiases); + RUN_TEST(test_loadModelFailsOnWrongFileTag); + RUN_TEST(test_clearModelSetsMembersToNull); + RUN_TEST(test_predictReturnsCorrectLabels); + + return UNITY_END(); +} \ No newline at end of file -- 2.47.2