Compare commits
25
Commits
nico
..
1c6786f157
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c6786f157 | ||
|
|
5d883d1e35 | ||
|
|
8efd1a3bfb | ||
|
|
db0fc41920 | ||
|
|
84fd3cfbd1 | ||
|
|
4e238675c8 | ||
|
|
284a313751 | ||
|
|
c7c68a0ce0 | ||
|
|
c0760a6646 | ||
|
|
34a471bda6 | ||
|
|
f8035cc4db | ||
|
|
db7617e046 | ||
|
|
47ff0906cc | ||
|
|
fe0728da3e | ||
|
|
dafd526828 | ||
|
|
e67fb83d8f | ||
|
|
2f6a3a4391 | ||
|
|
e60586a42b | ||
|
|
7dd22e8319 | ||
|
|
e4d2215465 | ||
|
|
c58801f245 | ||
|
|
12fd42ce7d | ||
|
|
d479ed4000 | ||
|
|
a70da6d7ca | ||
|
|
695f0c087c |
+8
-21
@@ -1,21 +1,8 @@
|
|||||||
I2_Wortsalat/Start_Mac/wordsalad_initial
|
# Alles ignorieren
|
||||||
I2_Wortsalat/Start_Mac/.DS_Store
|
*
|
||||||
I2_Wortsalat/.DS_Store
|
|
||||||
I2_Wortsalat/Start_Mac/input.o
|
# Ausnahmen: bestimmte Dateitypen in allen Verzeichnissen
|
||||||
.gitignore
|
!**/*.c
|
||||||
I2_Wortsalat/Start_Mac/game.o
|
!**/*.h
|
||||||
I2_Wortsalat/Start_Mac/runTests
|
!**/*Makefile
|
||||||
.o
|
!**/*.md
|
||||||
.a
|
|
||||||
.idea/editor.xml
|
|
||||||
.idea/vcs.xml
|
|
||||||
.idea/workspace.xml
|
|
||||||
I2_Wortsalat/.idea/editor.xml
|
|
||||||
I2_Wortsalat/.idea/I2_Wortsalat.iml
|
|
||||||
I2_Wortsalat/.idea/modules.xml
|
|
||||||
I2_Wortsalat/Start_Mac/main.o
|
|
||||||
I2_Wortsalat/Start_Mac/.DS_Store
|
|
||||||
I2_Wortsalat/Start_Mac/.DS_Store
|
|
||||||
I2_Wortsalat/Start_Mac/wordsalad
|
|
||||||
I2_Wortsalat/Start_Mac/graphicalGame.o
|
|
||||||
.DS_Store
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
Neurales Netzwerk zur erkennung von Zahlen in Bildern
|
||||||
|
-----------------------------------------------------
|
||||||
|
|
||||||
|
### Todo
|
||||||
|
|
||||||
|
1. matrix.h -> Matrix-Struktur definieren
|
||||||
|
2. matrix.c -> Funktionen zur verwaltung der Matritzen vervollständigen
|
||||||
|
- createMatrix -> leere MAtrix anlegen und SPeicher zuweisen
|
||||||
|
- clearMatrix -> löschen und Speicher freigeben
|
||||||
|
- setMatrixAt -> ein Matrix Element auf bestimmten Wert setzen
|
||||||
|
- getMatrixAt -> Wert eines Matrix Elements auslesen
|
||||||
|
- add
|
||||||
|
-> 2 Matritzen mit _gleichen Dimensionen_ Elementweise Addieren
|
||||||
|
-> ODER: wenn _eine_ Spaltenzahl 1 ist addition der Einspalten-Matrix als Vektor auf jede Spalte der Mehrspalten-Matrix
|
||||||
|
- multiply -> 2 Matritzen muliplizieren, wenn die Spaltenzahl der ersten so groß ist, wie die Zeilenzahl der zweiten
|
||||||
|
3. imageInputTests.c -> geeignete Tests entwerfen, um readImages später zu testen
|
||||||
|
4. imageInput -> einlesen eines Bildes
|
||||||
|
5. neuralNetworkTests.c -> prepareNeuronalNetworkFile() implementieren (aus neuralNetwork.c loadModel() rauslesen)
|
||||||
@@ -8,15 +8,116 @@
|
|||||||
|
|
||||||
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
||||||
|
|
||||||
|
/* ---------------- Hilfsfunktionen ---------------- */
|
||||||
|
|
||||||
|
static int readHeader(FILE *file, unsigned short *count, unsigned short *width, unsigned short *height)
|
||||||
|
{
|
||||||
|
unsigned short headerlength = strlen(FILE_HEADER_STRING);
|
||||||
|
char buffer[headerlength + 1];
|
||||||
|
if (fread(buffer, 1, headerlength, file) != headerlength)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
buffer[headerlength] = '\0';
|
||||||
|
|
||||||
|
if (strcmp(buffer, FILE_HEADER_STRING) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(count, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(width, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(height, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readSingleImage(FILE *file, GrayScaleImage *image)
|
||||||
|
{
|
||||||
|
unsigned int pixelCount = image->width * image->height;
|
||||||
|
|
||||||
|
if (fread(image->buffer, sizeof(GrayScalePixelType), pixelCount, file) != pixelCount)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
||||||
GrayScaleImageSeries *readImages(const char *path)
|
GrayScaleImageSeries *readImages(const char *path)
|
||||||
{
|
{
|
||||||
GrayScaleImageSeries *series = NULL;
|
FILE *file = fopen(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
unsigned short count, width, height;
|
||||||
|
|
||||||
|
if (!readHeader(file, &count, &width, &height)) {
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
|
||||||
|
series->count = count;
|
||||||
|
series->images = malloc(count * sizeof(GrayScaleImage));
|
||||||
|
series->labels = malloc(count * sizeof(unsigned char));
|
||||||
|
|
||||||
|
if (!series || !series->images || !series->labels)
|
||||||
|
{
|
||||||
|
free(series->images);
|
||||||
|
free(series->labels);
|
||||||
|
free(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
series->images[i].width = width;
|
||||||
|
series->images[i].height = height;
|
||||||
|
series->images[i].buffer = malloc(width * height * sizeof(GrayScalePixelType));
|
||||||
|
|
||||||
|
//malloc prüfen
|
||||||
|
if (!series->images[i].buffer)
|
||||||
|
{
|
||||||
|
clearSeries(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Image einlesen + prüfen
|
||||||
|
if (!readSingleImage(file, &series->images[i])) {
|
||||||
|
fclose(file);
|
||||||
|
clearSeries(series);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// label einlesen
|
||||||
|
if (fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1) {
|
||||||
|
fclose(file);
|
||||||
|
clearSeries(series);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
||||||
void clearSeries(GrayScaleImageSeries *series)
|
void clearSeries(GrayScaleImageSeries *series)
|
||||||
{
|
{
|
||||||
|
if(series)
|
||||||
|
{
|
||||||
|
for(unsigned int i = 0; i < series->count; i++)
|
||||||
|
{
|
||||||
|
free(series->images[i].buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(series->images);
|
||||||
|
free(series->labels);
|
||||||
|
free(series);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,10 +13,15 @@ static void prepareImageFile(const char *path, unsigned short int width, unsigne
|
|||||||
if(file != NULL)
|
if(file != NULL)
|
||||||
{
|
{
|
||||||
const char *fileTag = "__info2_image_file_format__";
|
const char *fileTag = "__info2_image_file_format__";
|
||||||
GrayScalePixelType *zeroBuffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));
|
GrayScalePixelType *buffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));
|
||||||
|
|
||||||
if(zeroBuffer != NULL)
|
if(buffer != NULL)
|
||||||
{
|
{
|
||||||
|
for(unsigned int i = 0; i < (numberOfImages * width * height); i++)
|
||||||
|
{
|
||||||
|
buffer[i] = (GrayScalePixelType)i;
|
||||||
|
}
|
||||||
|
|
||||||
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
||||||
fwrite(&numberOfImages, sizeof(numberOfImages), 1, file);
|
fwrite(&numberOfImages, sizeof(numberOfImages), 1, file);
|
||||||
fwrite(&width, sizeof(width), 1, file);
|
fwrite(&width, sizeof(width), 1, file);
|
||||||
@@ -24,11 +29,11 @@ static void prepareImageFile(const char *path, unsigned short int width, unsigne
|
|||||||
|
|
||||||
for(int i = 0; i < numberOfImages; i++)
|
for(int i = 0; i < numberOfImages; i++)
|
||||||
{
|
{
|
||||||
fwrite(zeroBuffer, sizeof(GrayScalePixelType), width * height, file);
|
fwrite(buffer, sizeof(GrayScalePixelType), width * height, file);
|
||||||
fwrite(&label, sizeof(unsigned char), 1, file);
|
fwrite(&label, sizeof(unsigned char), 1, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(zeroBuffer);
|
free(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
@@ -54,7 +59,7 @@ 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, 8, expectedWidth, 2, 1);
|
prepareImageFile(path, expectedWidth, 8, 2, 1);
|
||||||
series = readImages(path);
|
series = readImages(path);
|
||||||
TEST_ASSERT_NOT_NULL(series);
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
TEST_ASSERT_NOT_NULL(series->images);
|
TEST_ASSERT_NOT_NULL(series->images);
|
||||||
@@ -70,7 +75,7 @@ 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, expectedHeight, 8, 2, 1);
|
prepareImageFile(path, 8, expectedHeight, 2, 1);
|
||||||
series = readImages(path);
|
series = readImages(path);
|
||||||
TEST_ASSERT_NOT_NULL(series);
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
TEST_ASSERT_NOT_NULL(series->images);
|
TEST_ASSERT_NOT_NULL(series->images);
|
||||||
@@ -119,6 +124,27 @@ void test_readImagesFailsOnWrongFileTag(void)
|
|||||||
remove(path);
|
remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_readImagesReadsCorrectGrayScales(void)
|
||||||
|
{
|
||||||
|
GrayScaleImageSeries *series = NULL;
|
||||||
|
const char *path = "testFile.info2";
|
||||||
|
|
||||||
|
prepareImageFile(path, 8, 8, 1, 1);
|
||||||
|
series = readImages(path);
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
|
TEST_ASSERT_NOT_NULL(series->images);
|
||||||
|
TEST_ASSERT_EQUAL_UINT16(1, series->count);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < (8 * 8); i++)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQUAL_UINT8((GrayScalePixelType)i, series->images->buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSeries(series);
|
||||||
|
remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
void setUp(void) {
|
void setUp(void) {
|
||||||
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
||||||
}
|
}
|
||||||
@@ -138,6 +164,7 @@ int main()
|
|||||||
RUN_TEST(test_readImagesReturnsCorrectLabels);
|
RUN_TEST(test_readImagesReturnsCorrectLabels);
|
||||||
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
||||||
RUN_TEST(test_readImagesFailsOnWrongFileTag);
|
RUN_TEST(test_readImagesFailsOnWrongFileTag);
|
||||||
|
RUN_TEST(test_readImagesReadsCorrectGrayScales);
|
||||||
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
@@ -6,30 +6,146 @@
|
|||||||
|
|
||||||
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));
|
||||||
|
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearMatrix(Matrix *matrix)
|
void clearMatrix(Matrix *matrix)
|
||||||
{
|
{
|
||||||
|
if (matrix != NULL)
|
||||||
|
{
|
||||||
|
if (matrix->buffer != NULL)
|
||||||
|
{
|
||||||
|
free(matrix->buffer);
|
||||||
|
matrix->buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix->rows = 0;
|
||||||
|
matrix->cols = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (matrix.buffer != NULL)
|
||||||
|
{
|
||||||
|
if (rowIdx < matrix.rows && colIdx < matrix.cols)
|
||||||
|
{
|
||||||
|
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||||
{
|
{
|
||||||
|
if (matrix.buffer == NULL || rowIdx >= matrix.rows || colIdx >= matrix.cols)
|
||||||
|
{
|
||||||
|
return UNDEFINED_MATRIX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
|
|
||||||
|
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.rows != matrix2.rows)
|
||||||
|
{
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix1.cols == matrix2.cols)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix1.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, j);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix1.cols == 1 && matrix2.cols > 1)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix2.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, 0) + getMatrixAt(matrix2, i, j);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (matrix2.cols == 1 && matrix1.cols > 1)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix1.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, 0);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fall: Unterschiedliche Spaltenanzahl, beide ungleich 1
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
}
|
|
||||||
|
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.cols != matrix2.rows)
|
||||||
|
{
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||||
|
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix2.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType sum = 0;
|
||||||
|
|
||||||
|
for (int k = 0; k < matrix1.cols; k++)
|
||||||
|
{
|
||||||
|
sum += getMatrixAt(matrix1, i, k) * getMatrixAt(matrix2, k, j);
|
||||||
|
}
|
||||||
|
setMatrixAt(sum, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ typedef float MatrixType;
|
|||||||
|
|
||||||
// TODO Matrixtyp definieren
|
// TODO Matrixtyp definieren
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
MatrixType *buffer;
|
||||||
|
int rows;
|
||||||
|
int cols;
|
||||||
|
} Matrix;
|
||||||
|
|
||||||
|
|
||||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||||
void clearMatrix(Matrix *matrix);
|
void clearMatrix(Matrix *matrix);
|
||||||
|
|||||||
@@ -5,12 +5,80 @@
|
|||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
#include "neuralNetwork.h"
|
#include "neuralNetwork.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
################
|
||||||
|
Aufbau Test File
|
||||||
|
################
|
||||||
|
|
||||||
|
|
||||||
|
HEADER
|
||||||
|
|
||||||
|
inputDim
|
||||||
|
outputDim
|
||||||
|
|
||||||
|
-- Layer 1 --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
outputDim
|
||||||
|
|
||||||
|
-- Layer 2 --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
...
|
||||||
|
...
|
||||||
|
-- Layer n --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
outputDim = 0 => Ende
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||||
{
|
{
|
||||||
// TODO
|
FILE *file = fopen(path, "wb");
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
const char *fileTag = "__info2_neural_network_file_format__";
|
||||||
|
fwrite(fileTag, 1, strlen(fileTag), file);
|
||||||
|
|
||||||
|
//Stopt loadModel, falls keine Layer vorhanden
|
||||||
|
if (nn.numberOfLayers == 0)
|
||||||
|
{
|
||||||
|
int zero = 0;
|
||||||
|
fwrite(&zero, sizeof(int), 1, file);
|
||||||
|
fclose(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// input und output dimension schreiben
|
||||||
|
int inputDim = nn.layers[0].weights.cols;
|
||||||
|
fwrite(&inputDim, sizeof(int), 1, file);
|
||||||
|
|
||||||
|
// für weiter Layer nur outputDimension schreiben
|
||||||
|
for (unsigned int i = 0; i < nn.numberOfLayers; i++)
|
||||||
|
{
|
||||||
|
int outputDim = nn.layers[i].weights.rows;
|
||||||
|
fwrite(&outputDim, sizeof(int), 1, file);
|
||||||
|
|
||||||
|
int weightCount = nn.layers[i].weights.rows * nn.layers[i].weights.cols;
|
||||||
|
fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType), weightCount, file);
|
||||||
|
|
||||||
|
int biasesCount = nn.layers[i].biases.rows * nn.layers[i].biases.cols;
|
||||||
|
fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType), biasesCount, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadModel ließt 0 ein -> Stop
|
||||||
|
int fileEnd = 0;
|
||||||
|
fwrite(&fileEnd, sizeof(int), 1, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
|
|||||||
@@ -8,15 +8,116 @@
|
|||||||
|
|
||||||
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
||||||
|
|
||||||
|
/* ---------------- Hilfsfunktionen ---------------- */
|
||||||
|
|
||||||
|
static int readHeader(FILE *file, unsigned short *count, unsigned short *width, unsigned short *height)
|
||||||
|
{
|
||||||
|
unsigned short headerlength = strlen(FILE_HEADER_STRING);
|
||||||
|
char buffer[headerlength + 1];
|
||||||
|
if (fread(buffer, 1, headerlength, file) != headerlength)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
buffer[headerlength] = '\0';
|
||||||
|
|
||||||
|
if (strcmp(buffer, FILE_HEADER_STRING) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(count, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(width, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(height, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readSingleImage(FILE *file, GrayScaleImage *image)
|
||||||
|
{
|
||||||
|
unsigned int pixelCount = image->width * image->height;
|
||||||
|
|
||||||
|
if (fread(image->buffer, sizeof(GrayScalePixelType), pixelCount, file) != pixelCount)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
||||||
GrayScaleImageSeries *readImages(const char *path)
|
GrayScaleImageSeries *readImages(const char *path)
|
||||||
{
|
{
|
||||||
GrayScaleImageSeries *series = NULL;
|
FILE *file = fopen(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
unsigned short count, width, height;
|
||||||
|
|
||||||
|
if (!readHeader(file, &count, &width, &height)) {
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
|
||||||
|
series->count = count;
|
||||||
|
series->images = malloc(count * sizeof(GrayScaleImage));
|
||||||
|
series->labels = malloc(count * sizeof(unsigned char));
|
||||||
|
|
||||||
|
if (!series || !series->images || !series->labels)
|
||||||
|
{
|
||||||
|
free(series->images);
|
||||||
|
free(series->labels);
|
||||||
|
free(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
series->images[i].width = width;
|
||||||
|
series->images[i].height = height;
|
||||||
|
series->images[i].buffer = malloc(width * height * sizeof(GrayScalePixelType));
|
||||||
|
|
||||||
|
//malloc prüfen
|
||||||
|
if (!series->images[i].buffer)
|
||||||
|
{
|
||||||
|
clearSeries(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Image einlesen + prüfen
|
||||||
|
if (!readSingleImage(file, &series->images[i])) {
|
||||||
|
fclose(file);
|
||||||
|
clearSeries(series);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// label einlesen
|
||||||
|
if (fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1) {
|
||||||
|
fclose(file);
|
||||||
|
clearSeries(series);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
||||||
void clearSeries(GrayScaleImageSeries *series)
|
void clearSeries(GrayScaleImageSeries *series)
|
||||||
{
|
{
|
||||||
|
if(series)
|
||||||
|
{
|
||||||
|
for(unsigned int i = 0; i < series->count; i++)
|
||||||
|
{
|
||||||
|
free(series->images[i].buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(series->images);
|
||||||
|
free(series->labels);
|
||||||
|
free(series);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,10 +13,15 @@ static void prepareImageFile(const char *path, unsigned short int width, unsigne
|
|||||||
if(file != NULL)
|
if(file != NULL)
|
||||||
{
|
{
|
||||||
const char *fileTag = "__info2_image_file_format__";
|
const char *fileTag = "__info2_image_file_format__";
|
||||||
GrayScalePixelType *zeroBuffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));
|
GrayScalePixelType *buffer = (GrayScalePixelType *)calloc(numberOfImages * width * height, sizeof(GrayScalePixelType));
|
||||||
|
|
||||||
if(zeroBuffer != NULL)
|
if(buffer != NULL)
|
||||||
{
|
{
|
||||||
|
for(unsigned int i = 0; i < (numberOfImages * width * height); i++)
|
||||||
|
{
|
||||||
|
buffer[i] = (GrayScalePixelType)(i & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
fwrite(fileTag, sizeof(fileTag[0]), strlen(fileTag), file);
|
||||||
fwrite(&numberOfImages, sizeof(numberOfImages), 1, file);
|
fwrite(&numberOfImages, sizeof(numberOfImages), 1, file);
|
||||||
fwrite(&width, sizeof(width), 1, file);
|
fwrite(&width, sizeof(width), 1, file);
|
||||||
@@ -24,11 +29,11 @@ static void prepareImageFile(const char *path, unsigned short int width, unsigne
|
|||||||
|
|
||||||
for(int i = 0; i < numberOfImages; i++)
|
for(int i = 0; i < numberOfImages; i++)
|
||||||
{
|
{
|
||||||
fwrite(zeroBuffer, sizeof(GrayScalePixelType), width * height, file);
|
fwrite(buffer, sizeof(GrayScalePixelType), width * height, file);
|
||||||
fwrite(&label, sizeof(unsigned char), 1, file);
|
fwrite(&label, sizeof(unsigned char), 1, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(zeroBuffer);
|
free(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
fclose(file);
|
fclose(file);
|
||||||
@@ -54,7 +59,7 @@ 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, 8, expectedWidth, 2, 1);
|
prepareImageFile(path, expectedWidth, 8, 2, 1);
|
||||||
series = readImages(path);
|
series = readImages(path);
|
||||||
TEST_ASSERT_NOT_NULL(series);
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
TEST_ASSERT_NOT_NULL(series->images);
|
TEST_ASSERT_NOT_NULL(series->images);
|
||||||
@@ -70,7 +75,7 @@ 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, expectedHeight, 8, 2, 1);
|
prepareImageFile(path, 8, expectedHeight, 2, 1);
|
||||||
series = readImages(path);
|
series = readImages(path);
|
||||||
TEST_ASSERT_NOT_NULL(series);
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
TEST_ASSERT_NOT_NULL(series->images);
|
TEST_ASSERT_NOT_NULL(series->images);
|
||||||
@@ -119,6 +124,27 @@ void test_readImagesFailsOnWrongFileTag(void)
|
|||||||
remove(path);
|
remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void test_readImagesReadsCorrectGrayScales(void)
|
||||||
|
{
|
||||||
|
GrayScaleImageSeries *series = NULL;
|
||||||
|
const char *path = "testFile.info2";
|
||||||
|
|
||||||
|
prepareImageFile(path, 8, 8, 1, 1);
|
||||||
|
series = readImages(path);
|
||||||
|
|
||||||
|
TEST_ASSERT_NOT_NULL(series);
|
||||||
|
TEST_ASSERT_NOT_NULL(series->images);
|
||||||
|
TEST_ASSERT_EQUAL_UINT16(1, series->count);
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < (8 * 8); i++)
|
||||||
|
{
|
||||||
|
TEST_ASSERT_EQUAL_UINT8((GrayScalePixelType)i, series->images->buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearSeries(series);
|
||||||
|
remove(path);
|
||||||
|
}
|
||||||
|
|
||||||
void setUp(void) {
|
void setUp(void) {
|
||||||
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
||||||
}
|
}
|
||||||
@@ -138,6 +164,7 @@ int main()
|
|||||||
RUN_TEST(test_readImagesReturnsCorrectLabels);
|
RUN_TEST(test_readImagesReturnsCorrectLabels);
|
||||||
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
RUN_TEST(test_readImagesReturnsNullOnNotExistingPath);
|
||||||
RUN_TEST(test_readImagesFailsOnWrongFileTag);
|
RUN_TEST(test_readImagesFailsOnWrongFileTag);
|
||||||
|
RUN_TEST(test_readImagesReadsCorrectGrayScales);
|
||||||
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
@@ -40,8 +40,8 @@ mnistVisualization.o: mnistVisualization.c
|
|||||||
matrixTests: matrix.o matrixTests.c
|
matrixTests: matrix.o matrixTests.c
|
||||||
$(CC) $(CFLAGS) -I$(unityfolder) -o runMatrixTests matrixTests.c matrix.o $(BINARIES)/libunity.a
|
$(CC) $(CFLAGS) -I$(unityfolder) -o runMatrixTests matrixTests.c matrix.o $(BINARIES)/libunity.a
|
||||||
|
|
||||||
neuralNetworkTests: neuralNetwork.o neuralNetworkTests.c
|
neuralNetworkTests: neuralNetwork.o neuralNetworkTests.c matrix.o
|
||||||
$(CC) $(CFLAGS) -I$(unityfolder) -o runNeuralNetworkTests neuralNetworkTests.c neuralNetwork.o $(BINARIES)/libunity.a
|
$(CC) $(CFLAGS) -I$(unityfolder) -o runNeuralNetworkTests neuralNetworkTests.c neuralNetwork.o matrix.o $(BINARIES)/libunity.a
|
||||||
|
|
||||||
imageInputTests: imageInput.o imageInputTests.c
|
imageInputTests: imageInput.o imageInputTests.c
|
||||||
$(CC) $(CFLAGS) -I$(unityfolder) -o runImageInputTests imageInputTests.c imageInput.o $(BINARIES)/libunity.a
|
$(CC) $(CFLAGS) -I$(unityfolder) -o runImageInputTests imageInputTests.c imageInput.o $(BINARIES)/libunity.a
|
||||||
|
|||||||
@@ -6,30 +6,146 @@
|
|||||||
|
|
||||||
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));
|
||||||
|
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearMatrix(Matrix *matrix)
|
void clearMatrix(Matrix *matrix)
|
||||||
{
|
{
|
||||||
|
if (matrix != NULL)
|
||||||
|
{
|
||||||
|
if (matrix->buffer != NULL)
|
||||||
|
{
|
||||||
|
free(matrix->buffer);
|
||||||
|
matrix->buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix->rows = 0;
|
||||||
|
matrix->cols = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (matrix.buffer != NULL)
|
||||||
|
{
|
||||||
|
if (rowIdx < matrix.rows && colIdx < matrix.cols)
|
||||||
|
{
|
||||||
|
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||||
{
|
{
|
||||||
|
if (matrix.buffer == NULL || rowIdx >= matrix.rows || colIdx >= matrix.cols)
|
||||||
|
{
|
||||||
|
return UNDEFINED_MATRIX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
|
|
||||||
|
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.rows != matrix2.rows)
|
||||||
|
{
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix1.cols == matrix2.cols)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix1.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, j);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix1.cols == 1 && matrix2.cols > 1)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix2.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, 0) + getMatrixAt(matrix2, i, j);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (matrix2.cols == 1 && matrix1.cols > 1)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix1.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, 0);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fall: Unterschiedliche Spaltenanzahl, beide ungleich 1
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
}
|
|
||||||
|
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.cols != matrix2.rows)
|
||||||
|
{
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||||
|
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix2.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType sum = 0;
|
||||||
|
|
||||||
|
for (int k = 0; k < matrix1.cols; k++)
|
||||||
|
{
|
||||||
|
sum += getMatrixAt(matrix1, i, k) * getMatrixAt(matrix2, k, j);
|
||||||
|
}
|
||||||
|
setMatrixAt(sum, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ typedef float MatrixType;
|
|||||||
|
|
||||||
// TODO Matrixtyp definieren
|
// TODO Matrixtyp definieren
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
MatrixType *buffer;
|
||||||
|
int rows;
|
||||||
|
int cols;
|
||||||
|
} Matrix;
|
||||||
|
|
||||||
|
|
||||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||||
void clearMatrix(Matrix *matrix);
|
void clearMatrix(Matrix *matrix);
|
||||||
|
|||||||
@@ -141,6 +141,32 @@ void test_setMatrixAtFailsOnIndicesOutOfRange(void)
|
|||||||
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, matrixToTest.buffer, matrixToTest.cols * matrixToTest.rows);
|
TEST_ASSERT_EQUAL_FLOAT_ARRAY(expectedResults, matrixToTest.buffer, matrixToTest.cols * matrixToTest.rows);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 setUp(void) {
|
void setUp(void) {
|
||||||
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
// Falls notwendig, kann hier Vorbereitungsarbeit gemacht werden
|
||||||
}
|
}
|
||||||
@@ -165,6 +191,7 @@ int main()
|
|||||||
RUN_TEST(test_getMatrixAtFailsOnIndicesOutOfRange);
|
RUN_TEST(test_getMatrixAtFailsOnIndicesOutOfRange);
|
||||||
RUN_TEST(test_setMatrixAtSetsCorrectValue);
|
RUN_TEST(test_setMatrixAtSetsCorrectValue);
|
||||||
RUN_TEST(test_setMatrixAtFailsOnIndicesOutOfRange);
|
RUN_TEST(test_setMatrixAtFailsOnIndicesOutOfRange);
|
||||||
|
RUN_TEST(test_addSupportsBroadcasting);
|
||||||
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
@@ -5,12 +5,80 @@
|
|||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
#include "neuralNetwork.h"
|
#include "neuralNetwork.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
################
|
||||||
|
Aufbau Test File
|
||||||
|
################
|
||||||
|
|
||||||
|
|
||||||
|
HEADER
|
||||||
|
|
||||||
|
inputDim
|
||||||
|
outputDim
|
||||||
|
|
||||||
|
-- Layer 1 --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
outputDim
|
||||||
|
|
||||||
|
-- Layer 2 --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
...
|
||||||
|
...
|
||||||
|
-- Layer n --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
outputDim = 0 => Ende
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||||
{
|
{
|
||||||
// TODO
|
FILE *file = fopen(path, "wb");
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
const char *fileTag = "__info2_neural_network_file_format__";
|
||||||
|
fwrite(fileTag, 1, strlen(fileTag), file);
|
||||||
|
|
||||||
|
//Stopt loadModel, falls keine Layer vorhanden
|
||||||
|
if (nn.numberOfLayers == 0)
|
||||||
|
{
|
||||||
|
int zero = 0;
|
||||||
|
fwrite(&zero, sizeof(int), 1, file);
|
||||||
|
fclose(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// input und output dimension schreiben
|
||||||
|
int inputDim = nn.layers[0].weights.cols;
|
||||||
|
fwrite(&inputDim, sizeof(int), 1, file);
|
||||||
|
|
||||||
|
// für weiter Layer nur outputDimension schreiben
|
||||||
|
for (unsigned int i = 0; i < nn.numberOfLayers; i++)
|
||||||
|
{
|
||||||
|
int outputDim = nn.layers[i].weights.rows;
|
||||||
|
fwrite(&outputDim, sizeof(int), 1, file);
|
||||||
|
|
||||||
|
int weightCount = nn.layers[i].weights.rows * nn.layers[i].weights.cols;
|
||||||
|
fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType), weightCount, file);
|
||||||
|
|
||||||
|
int biasesCount = nn.layers[i].biases.rows * nn.layers[i].biases.cols;
|
||||||
|
fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType), biasesCount, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadModel ließt 0 ein -> Stop
|
||||||
|
int fileEnd = 0;
|
||||||
|
fwrite(&fileEnd, sizeof(int), 1, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
|
|||||||
@@ -8,15 +8,116 @@
|
|||||||
|
|
||||||
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
||||||
|
|
||||||
|
/* ---------------- Hilfsfunktionen ---------------- */
|
||||||
|
|
||||||
|
static int readHeader(FILE *file, unsigned short *count, unsigned short *width, unsigned short *height)
|
||||||
|
{
|
||||||
|
unsigned short headerlength = strlen(FILE_HEADER_STRING);
|
||||||
|
char buffer[headerlength + 1];
|
||||||
|
if (fread(buffer, 1, headerlength, file) != headerlength)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
buffer[headerlength] = '\0';
|
||||||
|
|
||||||
|
if (strcmp(buffer, FILE_HEADER_STRING) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(count, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(width, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (fread(height, sizeof(unsigned short), 1, file) != 1)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int readSingleImage(FILE *file, GrayScaleImage *image)
|
||||||
|
{
|
||||||
|
unsigned int pixelCount = image->width * image->height;
|
||||||
|
|
||||||
|
if (fread(image->buffer, sizeof(GrayScalePixelType), pixelCount, file) != pixelCount)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
||||||
GrayScaleImageSeries *readImages(const char *path)
|
GrayScaleImageSeries *readImages(const char *path)
|
||||||
{
|
{
|
||||||
GrayScaleImageSeries *series = NULL;
|
FILE *file = fopen(path, "rb");
|
||||||
|
if (!file)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
unsigned short count, width, height;
|
||||||
|
|
||||||
|
if (!readHeader(file, &count, &width, &height)) {
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrayScaleImageSeries *series = malloc(sizeof(GrayScaleImageSeries));
|
||||||
|
series->count = count;
|
||||||
|
series->images = malloc(count * sizeof(GrayScaleImage));
|
||||||
|
series->labels = malloc(count * sizeof(unsigned char));
|
||||||
|
|
||||||
|
if (!series || !series->images || !series->labels)
|
||||||
|
{
|
||||||
|
free(series->images);
|
||||||
|
free(series->labels);
|
||||||
|
free(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
series->images[i].width = width;
|
||||||
|
series->images[i].height = height;
|
||||||
|
series->images[i].buffer = malloc(width * height * sizeof(GrayScalePixelType));
|
||||||
|
|
||||||
|
//malloc prüfen
|
||||||
|
if (!series->images[i].buffer)
|
||||||
|
{
|
||||||
|
clearSeries(series);
|
||||||
|
fclose(file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Image einlesen + prüfen
|
||||||
|
if (!readSingleImage(file, &series->images[i])) {
|
||||||
|
fclose(file);
|
||||||
|
clearSeries(series);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// label einlesen
|
||||||
|
if (fread(&series->labels[i], sizeof(unsigned char), 1, file) != 1) {
|
||||||
|
fclose(file);
|
||||||
|
clearSeries(series);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
return series;
|
return series;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
||||||
void clearSeries(GrayScaleImageSeries *series)
|
void clearSeries(GrayScaleImageSeries *series)
|
||||||
{
|
{
|
||||||
|
if(series)
|
||||||
|
{
|
||||||
|
for(unsigned int i = 0; i < series->count; i++)
|
||||||
|
{
|
||||||
|
free(series->images[i].buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(series->images);
|
||||||
|
free(series->labels);
|
||||||
|
free(series);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,30 +6,146 @@
|
|||||||
|
|
||||||
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));
|
||||||
|
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearMatrix(Matrix *matrix)
|
void clearMatrix(Matrix *matrix)
|
||||||
{
|
{
|
||||||
|
if (matrix != NULL)
|
||||||
|
{
|
||||||
|
if (matrix->buffer != NULL)
|
||||||
|
{
|
||||||
|
free(matrix->buffer);
|
||||||
|
matrix->buffer = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
matrix->rows = 0;
|
||||||
|
matrix->cols = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 (matrix.buffer != NULL)
|
||||||
|
{
|
||||||
|
if (rowIdx < matrix.rows && colIdx < matrix.cols)
|
||||||
|
{
|
||||||
|
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||||
{
|
{
|
||||||
|
if (matrix.buffer == NULL || rowIdx >= matrix.rows || colIdx >= matrix.cols)
|
||||||
|
{
|
||||||
|
return UNDEFINED_MATRIX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
|
|
||||||
|
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.rows != matrix2.rows)
|
||||||
|
{
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix1.cols == matrix2.cols)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix1.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, j);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matrix1.cols == 1 && matrix2.cols > 1)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix2.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, 0) + getMatrixAt(matrix2, i, j);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
else if (matrix2.cols == 1 && matrix1.cols > 1)
|
||||||
|
{
|
||||||
|
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix1.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType value = getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, 0);
|
||||||
|
setMatrixAt(value, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Fall: Unterschiedliche Spaltenanzahl, beide ungleich 1
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||||
{
|
{
|
||||||
|
Matrix result;
|
||||||
}
|
|
||||||
|
if (matrix1.buffer == NULL || matrix2.buffer == NULL || matrix1.cols != matrix2.rows)
|
||||||
|
{
|
||||||
|
result.rows = 0;
|
||||||
|
result.cols = 0;
|
||||||
|
result.buffer = NULL;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = createMatrix(matrix1.rows, matrix2.cols);
|
||||||
|
|
||||||
|
for (int i = 0; i < matrix1.rows; i++)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < matrix2.cols; j++)
|
||||||
|
{
|
||||||
|
MatrixType sum = 0;
|
||||||
|
|
||||||
|
for (int k = 0; k < matrix1.cols; k++)
|
||||||
|
{
|
||||||
|
sum += getMatrixAt(matrix1, i, k) * getMatrixAt(matrix2, k, j);
|
||||||
|
}
|
||||||
|
setMatrixAt(sum, result, i, j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,13 @@ typedef float MatrixType;
|
|||||||
|
|
||||||
// TODO Matrixtyp definieren
|
// TODO Matrixtyp definieren
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
MatrixType *buffer;
|
||||||
|
int rows;
|
||||||
|
int cols;
|
||||||
|
} Matrix;
|
||||||
|
|
||||||
|
|
||||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||||
void clearMatrix(Matrix *matrix);
|
void clearMatrix(Matrix *matrix);
|
||||||
|
|||||||
@@ -5,12 +5,80 @@
|
|||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
#include "neuralNetwork.h"
|
#include "neuralNetwork.h"
|
||||||
|
|
||||||
|
/*
|
||||||
|
################
|
||||||
|
Aufbau Test File
|
||||||
|
################
|
||||||
|
|
||||||
|
|
||||||
|
HEADER
|
||||||
|
|
||||||
|
inputDim
|
||||||
|
outputDim
|
||||||
|
|
||||||
|
-- Layer 1 --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
outputDim
|
||||||
|
|
||||||
|
-- Layer 2 --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
...
|
||||||
|
...
|
||||||
|
-- Layer n --
|
||||||
|
weights
|
||||||
|
biases
|
||||||
|
|
||||||
|
outputDim = 0 => Ende
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||||
{
|
{
|
||||||
// TODO
|
FILE *file = fopen(path, "wb");
|
||||||
|
if (file)
|
||||||
|
{
|
||||||
|
const char *fileTag = "__info2_neural_network_file_format__";
|
||||||
|
fwrite(fileTag, 1, strlen(fileTag), file);
|
||||||
|
|
||||||
|
//Stopt loadModel, falls keine Layer vorhanden
|
||||||
|
if (nn.numberOfLayers == 0)
|
||||||
|
{
|
||||||
|
int zero = 0;
|
||||||
|
fwrite(&zero, sizeof(int), 1, file);
|
||||||
|
fclose(file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// input und output dimension schreiben
|
||||||
|
int inputDim = nn.layers[0].weights.cols;
|
||||||
|
fwrite(&inputDim, sizeof(int), 1, file);
|
||||||
|
|
||||||
|
// für weiter Layer nur outputDimension schreiben
|
||||||
|
for (unsigned int i = 0; i < nn.numberOfLayers; i++)
|
||||||
|
{
|
||||||
|
int outputDim = nn.layers[i].weights.rows;
|
||||||
|
fwrite(&outputDim, sizeof(int), 1, file);
|
||||||
|
|
||||||
|
int weightCount = nn.layers[i].weights.rows * nn.layers[i].weights.cols;
|
||||||
|
fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType), weightCount, file);
|
||||||
|
|
||||||
|
int biasesCount = nn.layers[i].biases.rows * nn.layers[i].biases.cols;
|
||||||
|
fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType), biasesCount, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadModel ließt 0 ein -> Stop
|
||||||
|
int fileEnd = 0;
|
||||||
|
fwrite(&fileEnd, sizeof(int), 1, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||||
{
|
{
|
||||||
const char *path = "some__nn_test_file.info2";
|
const char *path = "some__nn_test_file.info2";
|
||||||
|
|||||||
Submodule I2_Neuronales_Netz deleted from ff67cfdff5
@@ -1,9 +1,13 @@
|
|||||||
#include "input.h"
|
#include "input.h"
|
||||||
#include <string.h>
|
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// eine Funktion implementieren, die ein einzelnes Wort aus einer Textdatei (words.txt) einliest und als C-String zurückgibt.
|
// eine Funktion implementieren, die ein einzelnes Wort aus einer Textdatei
|
||||||
|
// (words.txt) einliest und als C-String zurückgibt.
|
||||||
|
|
||||||
|
// Bedingungen, die in main.c geprüft werden:
|
||||||
|
// - existiert die angegebene Datei
|
||||||
|
|
||||||
// Read words from file and store in 'words' array
|
// Read words from file and store in 'words' array
|
||||||
int readWords(FILE *file, char words[][MAX_WORD_LEN], unsigned int maxWordCount)
|
int readWords(FILE *file, char words[][MAX_WORD_LEN], unsigned int maxWordCount)
|
||||||
@@ -39,4 +43,4 @@ int readWords(FILE *file, char words[][MAX_WORD_LEN], unsigned int maxWordCount)
|
|||||||
|
|
||||||
|
|
||||||
return word_counter;
|
return word_counter;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user