Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2436240736 | |||
| fde82f2d9a | |||
| 0fc70f982c | |||
| b271c865cb | |||
|
|
077c6def78 |
Binary file not shown.
181
imageInput.c
181
imageInput.c
@ -6,190 +6,17 @@
|
|||||||
#define BUFFER_SIZE 100
|
#define BUFFER_SIZE 100
|
||||||
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
||||||
|
|
||||||
// =====================================================
|
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
||||||
// Hilfsfunktion 1
|
|
||||||
// Datei öffnen + Header prüfen + Metadaten lesen
|
|
||||||
// =====================================================
|
|
||||||
|
|
||||||
static FILE *openFileAndReadHeader(const char *path,
|
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
||||||
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)
|
GrayScaleImageSeries *readImages(const char *path)
|
||||||
{
|
{
|
||||||
unsigned short count = 0, width = 0, height = 0;
|
GrayScaleImageSeries *series = NULL;
|
||||||
|
|
||||||
// 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;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================
|
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
||||||
// Speicher-Freigabe
|
|
||||||
// =====================================================
|
|
||||||
void clearSeries(GrayScaleImageSeries *series)
|
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);
|
|
||||||
}
|
}
|
||||||
@ -54,7 +54,6 @@ void test_readImagesReturnsCorrectImageWidth(void)
|
|||||||
GrayScaleImageSeries *series = NULL;
|
GrayScaleImageSeries *series = NULL;
|
||||||
const unsigned short expectedWidth = 10;
|
const unsigned short expectedWidth = 10;
|
||||||
const char *path = "testFile.info2";
|
const char *path = "testFile.info2";
|
||||||
// prepareImageFile(path, width, height, numberOfImages, label)
|
|
||||||
prepareImageFile(path, expectedWidth, 8, 2, 1);
|
prepareImageFile(path, expectedWidth, 8, 2, 1);
|
||||||
series = readImages(path);
|
series = readImages(path);
|
||||||
TEST_ASSERT_NOT_NULL(series);
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
@ -71,7 +70,6 @@ void test_readImagesReturnsCorrectImageHeight(void)
|
|||||||
GrayScaleImageSeries *series = NULL;
|
GrayScaleImageSeries *series = NULL;
|
||||||
const unsigned short expectedHeight = 10;
|
const unsigned short expectedHeight = 10;
|
||||||
const char *path = "testFile.info2";
|
const char *path = "testFile.info2";
|
||||||
// prepareImageFile(path, width, height, numberOfImages, label)
|
|
||||||
prepareImageFile(path, 8, expectedHeight, 2, 1);
|
prepareImageFile(path, 8, expectedHeight, 2, 1);
|
||||||
series = readImages(path);
|
series = readImages(path);
|
||||||
TEST_ASSERT_NOT_NULL(series);
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
@ -121,161 +119,6 @@ void test_readImagesFailsOnWrongFileTag(void)
|
|||||||
remove(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; // INVALID
|
|
||||||
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; // INVALID
|
|
||||||
|
|
||||||
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 kein count Feld
|
|
||||||
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);
|
|
||||||
// count Feld nicht schreiben → EOF beim fread
|
|
||||||
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; // WICHTIG: width vor height (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) {
|
void setUp(void) {
|
||||||
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
||||||
}
|
}
|
||||||
@ -289,8 +132,6 @@ int main()
|
|||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
|
|
||||||
printf("\n============================\nImage input tests\n============================\n");
|
printf("\n============================\nImage input tests\n============================\n");
|
||||||
|
|
||||||
// Ursprüngliche Tests
|
|
||||||
RUN_TEST(test_readImagesReturnsCorrectNumberOfImages);
|
RUN_TEST(test_readImagesReturnsCorrectNumberOfImages);
|
||||||
RUN_TEST(test_readImagesReturnsCorrectImageWidth);
|
RUN_TEST(test_readImagesReturnsCorrectImageWidth);
|
||||||
RUN_TEST(test_readImagesReturnsCorrectImageHeight);
|
RUN_TEST(test_readImagesReturnsCorrectImageHeight);
|
||||||
@ -298,15 +139,5 @@ int main()
|
|||||||
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
||||||
RUN_TEST(test_readImagesFailsOnWrongFileTag);
|
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();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
105
matrix.c
105
matrix.c
@ -1,126 +1,35 @@
|
|||||||
#include "matrix.h"
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "matrix.h"
|
||||||
|
|
||||||
|
// TODO Matrix-Funktionen implementieren
|
||||||
|
|
||||||
// Matrix erstellen
|
|
||||||
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
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)
|
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)
|
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)
|
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 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 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;
|
|
||||||
}
|
}
|
||||||
9
matrix.h
9
matrix.h
@ -5,15 +5,9 @@
|
|||||||
|
|
||||||
typedef float MatrixType;
|
typedef float MatrixType;
|
||||||
|
|
||||||
// Struktur Matrix
|
// TODO Matrixtyp definieren
|
||||||
typedef struct {
|
|
||||||
MatrixType *buffer; // pointer
|
|
||||||
unsigned int rows;
|
|
||||||
unsigned int cols;
|
|
||||||
} Matrix;
|
|
||||||
|
|
||||||
|
|
||||||
// Funktionen
|
|
||||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||||
void clearMatrix(Matrix *matrix);
|
void clearMatrix(Matrix *matrix);
|
||||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx);
|
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx);
|
||||||
@ -21,4 +15,5 @@ MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int co
|
|||||||
Matrix add(const Matrix matrix1, const Matrix matrix2);
|
Matrix add(const Matrix matrix1, const Matrix matrix2);
|
||||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2);
|
Matrix multiply(const Matrix matrix1, const Matrix matrix2);
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -71,6 +71,32 @@ void test_addFailsOnDifferentInputDimensions(void)
|
|||||||
TEST_ASSERT_EQUAL_UINT32(0, result.cols);
|
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)
|
void test_multiplyReturnsCorrectResults(void)
|
||||||
{
|
{
|
||||||
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
|
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
|
||||||
@ -138,7 +164,7 @@ void test_setMatrixAtFailsOnIndicesOutOfRange(void)
|
|||||||
Matrix matrixToTest = {.rows=2, .cols=3, .buffer=buffer};
|
Matrix matrixToTest = {.rows=2, .cols=3, .buffer=buffer};
|
||||||
|
|
||||||
setMatrixAt(-1, matrixToTest, 2, 3);
|
setMatrixAt(-1, matrixToTest, 2, 3);
|
||||||
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, matrixToTest.buffer, matrixToTest.cols * matrixToTest.rows);
|
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, matrixToTest.buffer, sizeof(buffer)/sizeof(MatrixType));
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUp(void) {
|
void setUp(void) {
|
||||||
@ -159,6 +185,7 @@ int main()
|
|||||||
RUN_TEST(test_clearMatrixSetsMembersToNull);
|
RUN_TEST(test_clearMatrixSetsMembersToNull);
|
||||||
RUN_TEST(test_addReturnsCorrectResult);
|
RUN_TEST(test_addReturnsCorrectResult);
|
||||||
RUN_TEST(test_addFailsOnDifferentInputDimensions);
|
RUN_TEST(test_addFailsOnDifferentInputDimensions);
|
||||||
|
RUN_TEST(test_addSupportsBroadcasting);
|
||||||
RUN_TEST(test_multiplyReturnsCorrectResults);
|
RUN_TEST(test_multiplyReturnsCorrectResults);
|
||||||
RUN_TEST(test_multiplyFailsOnWrongInputDimensions);
|
RUN_TEST(test_multiplyFailsOnWrongInputDimensions);
|
||||||
RUN_TEST(test_getMatrixAtReturnsCorrectResult);
|
RUN_TEST(test_getMatrixAtReturnsCorrectResult);
|
||||||
|
|||||||
@ -254,8 +254,6 @@ unsigned char *predict(const NeuralNetwork model, const GrayScaleImage images[],
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void clearModel(NeuralNetwork *model)
|
void clearModel(NeuralNetwork *model)
|
||||||
{
|
{
|
||||||
if(model != NULL)
|
if(model != NULL)
|
||||||
|
|||||||
@ -1,219 +1,230 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
#include "neuralNetwork.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)
|
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||||
{
|
{
|
||||||
FILE *file = fopen(path, "wb");
|
// TODO
|
||||||
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)
|
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||||
{
|
{
|
||||||
const char *path = "test_nn_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
MatrixType buffer1[] = {1, 2, 3, 4, 5, 6};
|
||||||
MatrixType bBuf[] = {1,2,3};
|
MatrixType buffer2[] = {1, 2, 3, 4, 5, 6};
|
||||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
Matrix weights1 = {.buffer=buffer1, .rows=3, .cols=2};
|
||||||
NeuralNetwork nn = {layers,1};
|
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}};
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, nn);
|
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=2};
|
||||||
|
NeuralNetwork netUnderTest;
|
||||||
|
|
||||||
NeuralNetwork loaded = loadModel(path);
|
prepareNeuralNetworkFile(path, expectedNet);
|
||||||
TEST_ASSERT_EQUAL_INT(1, loaded.numberOfLayers);
|
|
||||||
clearModel(&loaded);
|
netUnderTest = loadModel(path);
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(expectedNet.numberOfLayers, netUnderTest.numberOfLayers);
|
||||||
|
clearModel(&netUnderTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------
|
|
||||||
// Test: Prüft Dimensionen der Gewichte
|
|
||||||
// --------------------------
|
|
||||||
void test_loadModelReturnsCorrectWeightDimensions(void)
|
void test_loadModelReturnsCorrectWeightDimensions(void)
|
||||||
{
|
{
|
||||||
const char *path = "test_nn_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
||||||
MatrixType bBuf[] = {1,2,3};
|
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
||||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
MatrixType biasBuffer[] = {7, 8, 9};
|
||||||
NeuralNetwork nn = {layers,1};
|
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
||||||
|
Layer layers[] = {{.weights=weights, .biases=biases}};
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, nn);
|
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
||||||
|
NeuralNetwork netUnderTest;
|
||||||
|
|
||||||
NeuralNetwork loaded = loadModel(path);
|
prepareNeuralNetworkFile(path, expectedNet);
|
||||||
TEST_ASSERT_EQUAL_INT(3, loaded.layers[0].weights.rows);
|
|
||||||
TEST_ASSERT_EQUAL_INT(2, loaded.layers[0].weights.cols);
|
netUnderTest = loadModel(path);
|
||||||
clearModel(&loaded);
|
|
||||||
remove(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------
|
|
||||||
// Test: Prüft Dimensionen der Biases
|
|
||||||
// --------------------------
|
|
||||||
void test_loadModelReturnsCorrectBiasDimensions(void)
|
void test_loadModelReturnsCorrectBiasDimensions(void)
|
||||||
{
|
{
|
||||||
const char *path = "test_nn_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
||||||
MatrixType bBuf[] = {1,2,3};
|
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
||||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
MatrixType biasBuffer[] = {7, 8, 9};
|
||||||
NeuralNetwork nn = {layers,1};
|
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
||||||
|
Layer layers[] = {{.weights=weights, .biases=biases}};
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, nn);
|
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
||||||
|
NeuralNetwork netUnderTest;
|
||||||
|
|
||||||
NeuralNetwork loaded = loadModel(path);
|
prepareNeuralNetworkFile(path, expectedNet);
|
||||||
TEST_ASSERT_EQUAL_INT(3, loaded.layers[0].biases.rows);
|
|
||||||
TEST_ASSERT_EQUAL_INT(1, loaded.layers[0].biases.cols);
|
netUnderTest = loadModel(path);
|
||||||
clearModel(&loaded);
|
|
||||||
remove(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------
|
|
||||||
// Test: Prüft, dass Gewichte korrekt geladen werden
|
|
||||||
// --------------------------
|
|
||||||
void test_loadModelReturnsCorrectWeights(void)
|
void test_loadModelReturnsCorrectWeights(void)
|
||||||
{
|
{
|
||||||
const char *path = "test_nn_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
||||||
MatrixType bBuf[] = {1,2,3};
|
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
||||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
MatrixType biasBuffer[] = {7, 8, 9};
|
||||||
NeuralNetwork nn = {layers,1};
|
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
||||||
|
Layer layers[] = {{.weights=weights, .biases=biases}};
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, nn);
|
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
||||||
|
NeuralNetwork netUnderTest;
|
||||||
|
|
||||||
NeuralNetwork loaded = loadModel(path);
|
prepareNeuralNetworkFile(path, expectedNet);
|
||||||
int n = loaded.layers[0].weights.rows * loaded.layers[0].weights.cols;
|
|
||||||
TEST_ASSERT_EQUAL_INT_ARRAY(wBuf, loaded.layers[0].weights.buffer, n);
|
netUnderTest = loadModel(path);
|
||||||
clearModel(&loaded);
|
|
||||||
remove(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------
|
|
||||||
// Test: Prüft, dass Bias korrekt geladen werden
|
|
||||||
// --------------------------
|
|
||||||
void test_loadModelReturnsCorrectBiases(void)
|
void test_loadModelReturnsCorrectBiases(void)
|
||||||
{
|
{
|
||||||
const char *path = "test_nn_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
MatrixType wBuf[] = {1,2,3,4,5,6};
|
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
||||||
MatrixType bBuf[] = {1,2,3};
|
Matrix weights = {.buffer=weightBuffer, .rows=3, .cols=2};
|
||||||
Layer layers[] = {{.weights={wBuf,3,2}, .biases={bBuf,3,1}}};
|
MatrixType biasBuffer[] = {7, 8, 9};
|
||||||
NeuralNetwork nn = {layers,1};
|
Matrix biases = {.buffer=biasBuffer, .rows=3, .cols=1};
|
||||||
|
Layer layers[] = {{.weights=weights, .biases=biases}};
|
||||||
|
|
||||||
prepareNeuralNetworkFile(path, nn);
|
NeuralNetwork expectedNet = {.layers=layers, .numberOfLayers=1};
|
||||||
|
NeuralNetwork netUnderTest;
|
||||||
|
|
||||||
NeuralNetwork loaded = loadModel(path);
|
prepareNeuralNetworkFile(path, expectedNet);
|
||||||
int n = loaded.layers[0].biases.rows * loaded.layers[0].biases.cols;
|
|
||||||
TEST_ASSERT_EQUAL_INT_ARRAY(bBuf, loaded.layers[0].biases.buffer, n);
|
netUnderTest = loadModel(path);
|
||||||
clearModel(&loaded);
|
|
||||||
remove(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------
|
|
||||||
// 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)
|
void test_loadModelFailsOnWrongFileTag(void)
|
||||||
{
|
{
|
||||||
const char *path = "wrong_nn_file.info2";
|
const char *path = "some_nn_test_file.info2";
|
||||||
|
NeuralNetwork netUnderTest;
|
||||||
FILE *file = fopen(path, "wb");
|
FILE *file = fopen(path, "wb");
|
||||||
|
|
||||||
if(file != NULL)
|
if(file != NULL)
|
||||||
{
|
{
|
||||||
const char *wrongTag = "wrong_header_string";
|
const char *fileTag = "info2_neural_network_file_format";
|
||||||
fwrite(wrongTag, sizeof(char), strlen(wrongTag), file);
|
|
||||||
|
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
NeuralNetwork nn = loadModel(path);
|
netUnderTest = loadModel(path);
|
||||||
TEST_ASSERT_NULL(nn.layers);
|
|
||||||
TEST_ASSERT_EQUAL_INT(0, nn.numberOfLayers);
|
|
||||||
remove(path);
|
remove(path);
|
||||||
|
|
||||||
|
TEST_ASSERT_NULL(netUnderTest.layers);
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, netUnderTest.numberOfLayers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------
|
void test_clearModelSetsMembersToNull(void)
|
||||||
// Unity Setup / Teardown
|
{
|
||||||
// --------------------------
|
const char *path = "some__nn_test_file.info2";
|
||||||
void setUp(void) {}
|
MatrixType weightBuffer[] = {1, 2, 3, 4, 5, 6};
|
||||||
void tearDown(void) {}
|
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};
|
||||||
// Hauptfunktion zum Ausführen der Tests
|
NeuralNetwork netUnderTest;
|
||||||
// --------------------------
|
|
||||||
int main(void)
|
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();
|
UNITY_BEGIN();
|
||||||
|
|
||||||
@ -223,9 +234,9 @@ int main(void)
|
|||||||
RUN_TEST(test_loadModelReturnsCorrectBiasDimensions);
|
RUN_TEST(test_loadModelReturnsCorrectBiasDimensions);
|
||||||
RUN_TEST(test_loadModelReturnsCorrectWeights);
|
RUN_TEST(test_loadModelReturnsCorrectWeights);
|
||||||
RUN_TEST(test_loadModelReturnsCorrectBiases);
|
RUN_TEST(test_loadModelReturnsCorrectBiases);
|
||||||
RUN_TEST(test_predictReturnsCorrectLabels);
|
|
||||||
RUN_TEST(test_clearModelSetsMembersToNull);
|
|
||||||
RUN_TEST(test_loadModelFailsOnWrongFileTag);
|
RUN_TEST(test_loadModelFailsOnWrongFileTag);
|
||||||
|
RUN_TEST(test_clearModelSetsMembersToNull);
|
||||||
|
RUN_TEST(test_predictReturnsCorrectLabels);
|
||||||
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user