Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b802e24abd | ||
|
|
37d6d88ddf | ||
|
|
a50550f3ea | ||
|
|
ad32be997b | ||
|
|
46601b3020 | ||
|
|
339c3e81b1 | ||
|
|
b7e44e9670 | ||
|
|
c76b9bc927 |
No files matched your search
+3
-60
@@ -1,61 +1,4 @@
|
||||
# ---> C
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
mnist
|
||||
runTests
|
||||
*.o
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
Startcode/mnist
|
||||
Startcode/runTests
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# IDE folders
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.exe
|
||||
Binary file not shown.
@@ -1,22 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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)
|
||||
{
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "imageInput.h"
|
||||
|
||||
|
||||
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");
|
||||
|
||||
if(file != NULL)
|
||||
{
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
GrayScalePixelType *zeroBuffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));
|
||||
|
||||
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);
|
||||
|
||||
for(int i = 0; i < numberOfImages; i++)
|
||||
{
|
||||
fwrite(zeroBuffer, sizeof(GrayScalePixelType), width * height, file);
|
||||
fwrite(&label, sizeof(unsigned char), 1, file);
|
||||
}
|
||||
|
||||
free(zeroBuffer);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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_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_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_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);
|
||||
}
|
||||
|
||||
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 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============================\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();
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "matrix.h"
|
||||
|
||||
// TODO Matrix-Funktionen implementieren
|
||||
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void clearMatrix(Matrix *matrix)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include "unity.h"
|
||||
#include "neuralNetwork.h"
|
||||
|
||||
|
||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "imageInput.h"
|
||||
|
||||
#define BUFFER_SIZE 100
|
||||
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
||||
|
||||
// =====================================================
|
||||
// Hilfsfunktion 1
|
||||
// Datei öffnen + Header prüfen + Metadaten lesen
|
||||
// =====================================================
|
||||
|
||||
static FILE *openFileAndReadHeader(const char *path, unsigned short *count, unsigned short *width, unsigned short *height)
|
||||
{
|
||||
// Schritt 1: Datei öffnen
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "Error: Cannot open file '%s'\n", path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 2: Header-String prüfen
|
||||
char buffer[BUFFER_SIZE] = {0};
|
||||
size_t headerLen = strlen(FILE_HEADER_STRING);
|
||||
|
||||
if (fread(buffer, sizeof(char), headerLen, file) != headerLen) {
|
||||
fprintf(stderr, "Error: Cannot read file header (file too small?)\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (strncmp(buffer, FILE_HEADER_STRING, headerLen) != 0) {
|
||||
fprintf(stderr, "Error: Invalid file header. Expected '%s', got: %.24s\n",
|
||||
FILE_HEADER_STRING, buffer);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 3: Metadaten lesen (Reihenfolge: count, width, height)
|
||||
// WICHTIG: Diese Reihenfolge (Anzahl, Breite, Höhe) entspricht
|
||||
// der Aufgabenstellung und dem in den Tests verwendeten Format.
|
||||
if (fread(count, sizeof(unsigned short), 1, file) != 1) {
|
||||
fprintf(stderr, "Error: Cannot read image count\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
if (fread(width, sizeof(unsigned short), 1, file) != 1) {
|
||||
fprintf(stderr, "Error: Cannot read image width\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
if (fread(height, sizeof(unsigned short), 1, file) != 1) {
|
||||
fprintf(stderr, "Error: Cannot read image height\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Input-Validierung: Prüfe auf ungültige Dimensionen
|
||||
if (*count == 0) {
|
||||
fprintf(stderr, "Error: Image count is 0\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
if (*width == 0) {
|
||||
fprintf(stderr, "Error: Image width is 0\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
if (*height == 0) {
|
||||
fprintf(stderr, "Error: Image height is 0\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Erfolg: offene Datei zurückgeben, Position ist nach Metadaten
|
||||
return file;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Hilfsfunktion 2: Speicher für die gesamte Serie anlegen
|
||||
// -----------------------------------------------------
|
||||
static GrayScaleImageSeries *allocateSeries(unsigned short count,
|
||||
unsigned short width,
|
||||
unsigned short height)
|
||||
{
|
||||
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
|
||||
if (!series) return NULL;
|
||||
|
||||
series->count = count;
|
||||
series->images = calloc(count, sizeof(GrayScaleImage));
|
||||
series->labels = calloc(count, sizeof(unsigned char));
|
||||
|
||||
if (!series->images || !series->labels)
|
||||
{
|
||||
clearSeries(series);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Bilddimensionen in jedes Struktur-Element übernehmen
|
||||
for (unsigned short i = 0; i < count; i++)
|
||||
{
|
||||
series->images[i].width = width;
|
||||
series->images[i].height = height;
|
||||
}
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------
|
||||
// Hilfsfunktion 3: EIN BILD + EIN LABEL lesen
|
||||
// -----------------------------------------------------
|
||||
static int readSingleImage(FILE *file, GrayScaleImage *img, unsigned char *label)
|
||||
{
|
||||
unsigned int pixelCount = img->width * img->height;
|
||||
|
||||
img->buffer = malloc(pixelCount * sizeof(GrayScalePixelType));
|
||||
if (!img->buffer)
|
||||
return 0;
|
||||
|
||||
if (fread(img->buffer, sizeof(GrayScalePixelType), pixelCount, file) != pixelCount)
|
||||
return 0;
|
||||
|
||||
if (fread(label, sizeof(unsigned char), 1, file) != 1)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Hauptfunktion: Liest komplette Bilderserie
|
||||
// =====================================================
|
||||
GrayScaleImageSeries *readImages(const char *path)
|
||||
{
|
||||
unsigned short count = 0, width = 0, height = 0;
|
||||
|
||||
// Schritt 1-3: Datei öffnen + Header + Metadaten
|
||||
FILE *file = openFileAndReadHeader(path, &count, &width, &height);
|
||||
if (!file) {
|
||||
// Fehler bereits geloggt von openFileAndReadHeader()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 4: Bilderserie allokieren
|
||||
GrayScaleImageSeries *series = allocateSeries(count, width, height);
|
||||
if (!series) {
|
||||
fprintf(stderr, "Error: Cannot allocate image series\n");
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 5: Alle Bilder + Labels lesen
|
||||
for (unsigned short i = 0; i < count; i++) {
|
||||
if (!readSingleImage(file, &series->images[i], &series->labels[i])) {
|
||||
fprintf(stderr, "Error: Cannot read image %u\n", i);
|
||||
clearSeries(series);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
return series;
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Speicher-Freigabe
|
||||
// =====================================================
|
||||
void clearSeries(GrayScaleImageSeries *series)
|
||||
{
|
||||
if (!series)
|
||||
return;
|
||||
|
||||
if (series->images)
|
||||
{
|
||||
for (unsigned int i = 0; i < series->count; i++)
|
||||
{
|
||||
free(series->images[i].buffer);
|
||||
series->images[i].buffer = NULL;
|
||||
}
|
||||
free(series->images);
|
||||
series->images = NULL;
|
||||
}
|
||||
|
||||
if (series->labels)
|
||||
{
|
||||
free(series->labels);
|
||||
series->labels = NULL;
|
||||
}
|
||||
|
||||
free(series);
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,311 @@
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "imageInput.h"
|
||||
|
||||
|
||||
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");
|
||||
|
||||
if(file != NULL)
|
||||
{
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
GrayScalePixelType *zeroBuffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));
|
||||
|
||||
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);
|
||||
|
||||
for(int i = 0; i < numberOfImages; i++)
|
||||
{
|
||||
fwrite(zeroBuffer, sizeof(GrayScalePixelType), width * height, file);
|
||||
fwrite(&label, sizeof(unsigned char), 1, file);
|
||||
}
|
||||
|
||||
free(zeroBuffer);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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_readImagesReturnsCorrectImageWidth(void)
|
||||
{
|
||||
GrayScaleImageSeries *series = NULL;
|
||||
const unsigned short expectedWidth = 10;
|
||||
const char *path = "testFile.info2";
|
||||
// prepareImageFile(path, width, height, numberOfImages, label)
|
||||
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_readImagesReturnsCorrectImageHeight(void)
|
||||
{
|
||||
GrayScaleImageSeries *series = NULL;
|
||||
const unsigned short expectedHeight = 10;
|
||||
const char *path = "testFile.info2";
|
||||
// prepareImageFile(path, width, height, numberOfImages, label)
|
||||
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_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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// =====================================================
|
||||
// Tests für Hilfsfunktion imageInput.c
|
||||
// =====================================================
|
||||
|
||||
void test_openFileAndReadHeaderFailsOnZeroImageCount(void)
|
||||
{
|
||||
// Test: Datei mit count=0 sollte fehlschlagen
|
||||
const char *path = "testZeroCount.info2";
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (file != NULL) {
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
unsigned short zero_count = 0;
|
||||
unsigned short width = 28;
|
||||
unsigned short height = 28;
|
||||
|
||||
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
||||
fwrite(&zero_count, sizeof(unsigned short), 1, file);
|
||||
fwrite(&height, sizeof(unsigned short), 1, file);
|
||||
fwrite(&width, sizeof(unsigned short), 1, file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
// readImages sollte NULL zurückgeben bei count=0
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_openFileAndReadHeaderFailsOnZeroWidth(void)
|
||||
{
|
||||
// Test: Datei mit width=0 sollte fehlschlagen
|
||||
const char *path = "testZeroWidth.info2";
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (file != NULL) {
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
unsigned short count = 5;
|
||||
unsigned short width = 0;
|
||||
unsigned short height = 28;
|
||||
|
||||
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
||||
fwrite(&count, sizeof(unsigned short), 1, file);
|
||||
fwrite(&height, sizeof(unsigned short), 1, file);
|
||||
fwrite(&width, sizeof(unsigned short), 1, file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_openFileAndReadHeaderFailsOnZeroHeight(void)
|
||||
{
|
||||
// Test: Datei mit height=0 sollte fehlschlagen
|
||||
const char *path = "testZeroHeight.info2";
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (file != NULL) {
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
unsigned short count = 5;
|
||||
unsigned short width = 28;
|
||||
unsigned short height = 0;
|
||||
|
||||
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
||||
fwrite(&count, sizeof(unsigned short), 1, file);
|
||||
fwrite(&height, sizeof(unsigned short), 1, file);
|
||||
fwrite(&width, sizeof(unsigned short), 1, file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_openFileAndReadHeaderFailsOnTruncatedHeader(void)
|
||||
{
|
||||
// Test: Datei ist zu kurz für Header
|
||||
const char *path = "testTruncated.info2";
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (file != NULL) {
|
||||
// Nur 10 Bytes schreiben (Header ist 24 Bytes)
|
||||
const char *fileTag = "__info2_im";
|
||||
fwrite(fileTag, 1, 10, file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_openFileAndReadHeaderFailsOnMissingCount(void)
|
||||
{
|
||||
// Test: Datei hat Header aber keine Meta Daten
|
||||
const char *path = "testMissingCount.info2";
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (file != NULL) {
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
TEST_ASSERT_NULL(readImages(path));
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_openFileAndReadHeaderSucceedsWithValidData(void)
|
||||
{
|
||||
// Test: Valide Datei sollte erfolgreich sein
|
||||
const char *path = "testValid.info2";
|
||||
prepareImageFile(path, 28, 28, 5, 3);
|
||||
|
||||
GrayScaleImageSeries *series = readImages(path);
|
||||
TEST_ASSERT_NOT_NULL(series);
|
||||
TEST_ASSERT_EQUAL_UINT16(5, series->count);
|
||||
TEST_ASSERT_EQUAL_UINT16(28, series->images[0].width);
|
||||
TEST_ASSERT_EQUAL_UINT16(28, series->images[0].height);
|
||||
|
||||
clearSeries(series);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
void test_openFileAndReadHeaderCorrectMetadataOrder(void)
|
||||
{
|
||||
// Test: Metadaten werden in richtiger Reihenfolge gelesen
|
||||
const char *path = "testMetadataOrder.info2";
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (file != NULL) {
|
||||
const char *fileTag = "__info2_image_file_format__";
|
||||
unsigned short count = 10;
|
||||
unsigned short width = 16; // Anzahl, Breite, Höhe
|
||||
unsigned short height = 32;
|
||||
unsigned char label = 5;
|
||||
unsigned char pixel_data[16*32];
|
||||
memset(pixel_data, 128, sizeof(pixel_data));
|
||||
|
||||
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
||||
fwrite(&count, sizeof(unsigned short), 1, file);
|
||||
fwrite(&width, sizeof(unsigned short), 1, file);
|
||||
fwrite(&height, sizeof(unsigned short), 1, file);
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
fwrite(pixel_data, 1, 16*32, file);
|
||||
fwrite(&label, 1, 1, file);
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
GrayScaleImageSeries *series = readImages(path);
|
||||
TEST_ASSERT_NOT_NULL(series);
|
||||
TEST_ASSERT_EQUAL_UINT16(10, series->count);
|
||||
TEST_ASSERT_EQUAL_UINT16(32, series->images[0].height); // height korrekt
|
||||
TEST_ASSERT_EQUAL_UINT16(16, series->images[0].width); // width korrekt
|
||||
|
||||
clearSeries(series);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
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============================\nImage input tests\n============================\n");
|
||||
|
||||
// Ursprüngliche Tests
|
||||
RUN_TEST(test_readImagesReturnsCorrectNumberOfImages);
|
||||
RUN_TEST(test_readImagesReturnsCorrectImageWidth);
|
||||
RUN_TEST(test_readImagesReturnsCorrectImageHeight);
|
||||
RUN_TEST(test_readImagesReturnsCorrectLabels);
|
||||
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
||||
RUN_TEST(test_readImagesFailsOnWrongFileTag);
|
||||
|
||||
// Neue Tests für kombinierte Funktion (Input-Validierung)
|
||||
printf("\n--- Tests für Input-Validierung ---\n");
|
||||
RUN_TEST(test_openFileAndReadHeaderFailsOnZeroImageCount);
|
||||
RUN_TEST(test_openFileAndReadHeaderFailsOnZeroWidth);
|
||||
RUN_TEST(test_openFileAndReadHeaderFailsOnZeroHeight);
|
||||
RUN_TEST(test_openFileAndReadHeaderFailsOnTruncatedHeader);
|
||||
RUN_TEST(test_openFileAndReadHeaderFailsOnMissingCount);
|
||||
RUN_TEST(test_openFileAndReadHeaderSucceedsWithValidData);
|
||||
RUN_TEST(test_openFileAndReadHeaderCorrectMetadataOrder);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,126 @@
|
||||
#include "matrix.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
// Matrix erstellen
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
||||
{
|
||||
Matrix m;
|
||||
if (rows == 0 || cols == 0) {
|
||||
m.rows = 0;
|
||||
m.cols = 0;
|
||||
m.buffer = NULL;
|
||||
return m;
|
||||
}
|
||||
|
||||
m.rows = rows;
|
||||
m.cols = cols;
|
||||
m.buffer = (MatrixType*)calloc(rows * cols, sizeof(MatrixType));
|
||||
if (!m.buffer) {
|
||||
m.rows = 0;
|
||||
m.cols = 0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
// Speicher freigeben
|
||||
void clearMatrix(Matrix *matrix)
|
||||
{
|
||||
if (!matrix || !matrix->buffer) return;
|
||||
free(matrix->buffer);
|
||||
matrix->buffer = NULL;
|
||||
matrix->rows = 0;
|
||||
matrix->cols = 0;
|
||||
}
|
||||
|
||||
// Wert setzen
|
||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
if (rowIdx >= matrix.rows || colIdx >= matrix.cols) return;
|
||||
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
|
||||
}
|
||||
|
||||
// Wert auslesen
|
||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
if (rowIdx >= matrix.rows || colIdx >= matrix.cols) return UNDEFINED_MATRIX_VALUE;
|
||||
return matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||
}
|
||||
|
||||
// Addition (mit Broadcasting-Unterstützung für Bias)
|
||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
Matrix result;
|
||||
|
||||
// Fall 1: Exakte Dimensionen (Element-weise Addition)
|
||||
if (matrix1.rows == matrix2.rows && matrix1.cols == matrix2.cols) {
|
||||
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||
for (unsigned int i = 0; i < matrix1.rows * matrix1.cols; i++)
|
||||
result.buffer[i] = matrix1.buffer[i] + matrix2.buffer[i];
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fall 2: matrix1 ist (zeilen x 1) Spaltenvektor, matrix2 ist (zeilen x spalten)
|
||||
// Broadcasting: matrix1's Spalte wird zu jeder Spalte von matrix2 addiert
|
||||
if (matrix1.rows == matrix2.rows && matrix1.cols == 1) {
|
||||
result = createMatrix(matrix2.rows, matrix2.cols);
|
||||
for (unsigned int col = 0; col < matrix2.cols; col++) {
|
||||
for (unsigned int row = 0; row < matrix2.rows; row++) {
|
||||
MatrixType val1 = matrix1.buffer[row * matrix1.cols + 0];
|
||||
MatrixType val2 = matrix2.buffer[row * matrix2.cols + col];
|
||||
result.buffer[row * result.cols + col] = val1 + val2;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fall 3: matrix2 ist (zeilen x 1) Spaltenvektor, matrix1 ist (zeilen x spalten)
|
||||
// Broadcasting: matrix2's Spalte wird zu jeder Spalte von matrix1 addiert
|
||||
if (matrix2.rows == matrix1.rows && matrix2.cols == 1) {
|
||||
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||
for (unsigned int col = 0; col < matrix1.cols; col++) {
|
||||
for (unsigned int row = 0; row < matrix1.rows; row++) {
|
||||
MatrixType val1 = matrix1.buffer[row * matrix1.cols + col];
|
||||
MatrixType val2 = matrix2.buffer[row * matrix2.cols + 0];
|
||||
result.buffer[row * result.cols + col] = val1 + val2;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Ungültige Dimensionen - leere Matrix zurückgeben
|
||||
result.rows = 0;
|
||||
result.cols = 0;
|
||||
result.buffer = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Multiplikation
|
||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
Matrix result;
|
||||
|
||||
// Überprüfe ob Multiplikation möglich ist (Spalten matrix1 == Zeilen matrix2)
|
||||
if (matrix1.cols != matrix2.rows) {
|
||||
result.rows = 0;
|
||||
result.cols = 0;
|
||||
result.buffer = NULL;
|
||||
return result;
|
||||
}
|
||||
|
||||
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||
|
||||
// Berechne alle Elemente des Ergebnisses
|
||||
for (unsigned int i = 0; i < matrix1.rows; i++)
|
||||
{
|
||||
for (unsigned int j = 0; j < matrix2.cols; j++)
|
||||
{
|
||||
// Skalarprodukt: Reihe i von matrix1 × Spalte j von matrix2
|
||||
MatrixType sum = 0;
|
||||
for (unsigned int k = 0; k < matrix1.cols; k++)
|
||||
sum += matrix1.buffer[i * matrix1.cols + k] * matrix2.buffer[k * matrix2.cols + j];
|
||||
result.buffer[i * result.cols + j] = sum;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -5,9 +5,15 @@
|
||||
|
||||
typedef float MatrixType;
|
||||
|
||||
// TODO Matrixtyp definieren
|
||||
// Struktur Matrix
|
||||
typedef struct {
|
||||
MatrixType *buffer; // pointer
|
||||
unsigned int rows;
|
||||
unsigned int cols;
|
||||
} Matrix;
|
||||
|
||||
|
||||
// Funktionen
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||
void clearMatrix(Matrix *matrix);
|
||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx);
|
||||
@@ -15,5 +21,4 @@ MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int co
|
||||
Matrix add(const Matrix matrix1, const Matrix matrix2);
|
||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2);
|
||||
|
||||
|
||||
#endif
|
||||
@@ -71,32 +71,6 @@ void test_addFailsOnDifferentInputDimensions(void)
|
||||
TEST_ASSERT_EQUAL_UINT32(0, result.cols);
|
||||
}
|
||||
|
||||
void test_addSupportsBroadcasting(void)
|
||||
{
|
||||
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
|
||||
MatrixType buffer2[] = {7, 8};
|
||||
Matrix matrix1 = {.rows=2, .cols=3, .buffer=buffer1};
|
||||
Matrix matrix2 = {.rows=2, .cols=1, .buffer=buffer2};
|
||||
|
||||
Matrix result1 = add(matrix1, matrix2);
|
||||
Matrix result2 = add(matrix2, matrix1);
|
||||
|
||||
float expectedResults[] = {8, 9, 10, 12, 13, 14};
|
||||
|
||||
TEST_ASSERT_EQUAL_UINT32(matrix1.rows, result1.rows);
|
||||
TEST_ASSERT_EQUAL_UINT32(matrix1.cols, result1.cols);
|
||||
TEST_ASSERT_EQUAL_UINT32(matrix1.rows, result2.rows);
|
||||
TEST_ASSERT_EQUAL_UINT32(matrix1.cols, result2.cols);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(sizeof(expectedResults)/sizeof(expectedResults[0]), result1.rows * result1.cols);
|
||||
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, result1.buffer, result1.cols * result1.rows);
|
||||
TEST_ASSERT_EQUAL_INT(sizeof(expectedResults)/sizeof(expectedResults[0]), result2.rows * result2.cols);
|
||||
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, result2.buffer, result2.cols * result2.rows);
|
||||
|
||||
free(result1.buffer);
|
||||
free(result2.buffer);
|
||||
}
|
||||
|
||||
void test_multiplyReturnsCorrectResults(void)
|
||||
{
|
||||
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
|
||||
@@ -164,7 +138,7 @@ void test_setMatrixAtFailsOnIndicesOutOfRange(void)
|
||||
Matrix matrixToTest = {.rows=2, .cols=3, .buffer=buffer};
|
||||
|
||||
setMatrixAt(-1, matrixToTest, 2, 3);
|
||||
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, matrixToTest.buffer, sizeof(buffer)/sizeof(MatrixType));
|
||||
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, matrixToTest.buffer, matrixToTest.cols * matrixToTest.rows);
|
||||
}
|
||||
|
||||
void setUp(void) {
|
||||
@@ -185,7 +159,6 @@ int main()
|
||||
RUN_TEST(test_clearMatrixSetsMembersToNull);
|
||||
RUN_TEST(test_addReturnsCorrectResult);
|
||||
RUN_TEST(test_addFailsOnDifferentInputDimensions);
|
||||
RUN_TEST(test_addSupportsBroadcasting);
|
||||
RUN_TEST(test_multiplyReturnsCorrectResults);
|
||||
RUN_TEST(test_multiplyFailsOnWrongInputDimensions);
|
||||
RUN_TEST(test_getMatrixAtReturnsCorrectResult);
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -254,6 +254,8 @@ unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[],
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void clearModel(NeuralNetwork *model)
|
||||
{
|
||||
if(model != NULL)
|
||||
File renamed without changes.
@@ -0,0 +1,231 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "unity.h"
|
||||
#include "neuralNetwork.h"
|
||||
|
||||
#define FILE_HEADER_STRING "__info2_neural_network_file_format__"
|
||||
|
||||
|
||||
// --------------------------
|
||||
// Hilfsfunktion zum Erstellen einer Test-Datei für das Netzwerk
|
||||
// --------------------------
|
||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||
{
|
||||
FILE *file = fopen(path, "wb");
|
||||
if (!file) return;
|
||||
|
||||
// Dateikennzeichnung schreiben
|
||||
const char *fileTag = "__info2_neural_network_file_format__";
|
||||
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
|
||||
|
||||
// Alle Layer des Netzwerks in die Datei schreiben
|
||||
for (unsigned int i = 0; i < nn.numberOfLayers; i++)
|
||||
{
|
||||
unsigned int inputDim = nn.layers[i].weights.cols;
|
||||
unsigned int outputDim = nn.layers[i].weights.rows;
|
||||
|
||||
// Dimensionen des Layers schreiben
|
||||
fwrite(&inputDim, sizeof(unsigned int), 1, file);
|
||||
fwrite(&outputDim, sizeof(unsigned int), 1, file);
|
||||
|
||||
// Gewichtsmatrix schreiben
|
||||
fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType),
|
||||
nn.layers[i].weights.rows * nn.layers[i].weights.cols, file);
|
||||
|
||||
// Biasvektor schreiben
|
||||
fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType),
|
||||
nn.layers[i].biases.rows * nn.layers[i].biases.cols, file);
|
||||
}
|
||||
|
||||
// Markierung für das Datei-Ende (keine weiteren Layer)
|
||||
unsigned int zero = 0;
|
||||
fwrite(&zero, sizeof(unsigned int), 1, file);
|
||||
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// --------------------------
|
||||
// Test: Prüft, ob loadModel richtige Anzahl Layer lädt
|
||||
// --------------------------
|
||||
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||
{
|
||||
const char *path = "test_nn_file.info2";
|
||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||
MatrixType bBuf[] = {1,2,3};
|
||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||
NeuralNetwork nn = {layers,1};
|
||||
|
||||
prepareNeuralNetworkFile(path, nn);
|
||||
|
||||
NeuralNetwork loaded = loadModel(path);
|
||||
TEST_ASSERT_EQUAL_INT(1, loaded.numberOfLayers);
|
||||
clearModel(&loaded);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Test: Prüft Dimensionen der Gewichte
|
||||
// --------------------------
|
||||
void test_loadModelReturnsCorrectWeightDimensions(void)
|
||||
{
|
||||
const char *path = "test_nn_file.info2";
|
||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||
MatrixType bBuf[] = {1,2,3};
|
||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||
NeuralNetwork nn = {layers,1};
|
||||
|
||||
prepareNeuralNetworkFile(path, nn);
|
||||
|
||||
NeuralNetwork loaded = loadModel(path);
|
||||
TEST_ASSERT_EQUAL_INT(3, loaded.layers[0].weights.rows);
|
||||
TEST_ASSERT_EQUAL_INT(2, loaded.layers[0].weights.cols);
|
||||
clearModel(&loaded);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Test: Prüft Dimensionen der Biases
|
||||
// --------------------------
|
||||
void test_loadModelReturnsCorrectBiasDimensions(void)
|
||||
{
|
||||
const char *path = "test_nn_file.info2";
|
||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||
MatrixType bBuf[] = {1,2,3};
|
||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||
NeuralNetwork nn = {layers,1};
|
||||
|
||||
prepareNeuralNetworkFile(path, nn);
|
||||
|
||||
NeuralNetwork loaded = loadModel(path);
|
||||
TEST_ASSERT_EQUAL_INT(3, loaded.layers[0].biases.rows);
|
||||
TEST_ASSERT_EQUAL_INT(1, loaded.layers[0].biases.cols);
|
||||
clearModel(&loaded);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Test: Prüft, dass Gewichte korrekt geladen werden
|
||||
// --------------------------
|
||||
void test_loadModelReturnsCorrectWeights(void)
|
||||
{
|
||||
const char *path = "test_nn_file.info2";
|
||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||
MatrixType bBuf[] = {1,2,3};
|
||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||
NeuralNetwork nn = {layers,1};
|
||||
|
||||
prepareNeuralNetworkFile(path, nn);
|
||||
|
||||
NeuralNetwork loaded = loadModel(path);
|
||||
int n = loaded.layers[0].weights.rows * loaded.layers[0].weights.cols;
|
||||
TEST_ASSERT_EQUAL_INT_ARRAY(wBuf, loaded.layers[0].weights.buffer, n);
|
||||
clearModel(&loaded);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Test: Prüft, dass Bias korrekt geladen werden
|
||||
// --------------------------
|
||||
void test_loadModelReturnsCorrectBiases(void)
|
||||
{
|
||||
const char *path = "test_nn_file.info2";
|
||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||
MatrixType bBuf[] = {1,2,3};
|
||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||
NeuralNetwork nn = {layers,1};
|
||||
|
||||
prepareNeuralNetworkFile(path, nn);
|
||||
|
||||
NeuralNetwork loaded = loadModel(path);
|
||||
int n = loaded.layers[0].biases.rows * loaded.layers[0].biases.cols;
|
||||
TEST_ASSERT_EQUAL_INT_ARRAY(bBuf, loaded.layers[0].biases.buffer, n);
|
||||
clearModel(&loaded);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Test: predict Funktion
|
||||
// --------------------------
|
||||
void test_predictReturnsCorrectLabels(void)
|
||||
{
|
||||
GrayScalePixelType img1[] = {10,20,30,40};
|
||||
GrayScalePixelType img2[] = {5,15,25,35};
|
||||
GrayScaleImage images[] = {
|
||||
{.buffer=img1, .width=2, .height=2},
|
||||
{.buffer=img2, .width=2, .height=2}
|
||||
};
|
||||
|
||||
// Dummy Network für test: ReLU-ähnlich
|
||||
MatrixType w1[] = {1,0,0,1,1,0,0,1};
|
||||
MatrixType b1[] = {0,0};
|
||||
Layer layers[] = {{.weights={w1,2,4}, .biases={b1,2,1}, .activation=NULL}};
|
||||
NeuralNetwork nn = {layers,1};
|
||||
|
||||
unsigned char *labels = predict(nn, images, 2);
|
||||
TEST_ASSERT_NOT_NULL(labels);
|
||||
free(labels);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Test: clearModel setzt Pointer auf NULL
|
||||
// --------------------------
|
||||
void test_clearModelSetsMembersToNull(void)
|
||||
{
|
||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
||||
MatrixType bBuf[] = {1,2,3};
|
||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
||||
NeuralNetwork nn = {layers,1};
|
||||
|
||||
clearModel(&nn);
|
||||
TEST_ASSERT_NULL(nn.layers);
|
||||
TEST_ASSERT_EQUAL_INT(0, nn.numberOfLayers);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Test: Fehlerhafte Datei (Header falsch)
|
||||
// --------------------------
|
||||
void test_loadModelFailsOnWrongFileTag(void)
|
||||
{
|
||||
const char *path = "wrong_nn_file.info2";
|
||||
FILE *file = fopen(path, "wb");
|
||||
if(file != NULL)
|
||||
{
|
||||
const char *wrongTag = "wrong_header_string";
|
||||
fwrite(wrongTag, sizeof(char), strlen(wrongTag), file);
|
||||
fclose(file);
|
||||
}
|
||||
|
||||
NeuralNetwork nn = loadModel(path);
|
||||
TEST_ASSERT_NULL(nn.layers);
|
||||
TEST_ASSERT_EQUAL_INT(0, nn.numberOfLayers);
|
||||
remove(path);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Unity Setup / Teardown
|
||||
// --------------------------
|
||||
void setUp(void) {}
|
||||
void tearDown(void) {}
|
||||
|
||||
// --------------------------
|
||||
// Hauptfunktion zum Ausführen der Tests
|
||||
// --------------------------
|
||||
int main(void)
|
||||
{
|
||||
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_predictReturnsCorrectLabels);
|
||||
RUN_TEST(test_clearModelSetsMembersToNull);
|
||||
RUN_TEST(test_loadModelFailsOnWrongFileTag);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user