forked from freudenreichan/info2Praktikum-NeuronalesNetz
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
447e77a7bf | ||
|
|
a6749f07d1 |
No files matched your search
+60
-3
@@ -1,4 +1,61 @@
|
||||
mnist
|
||||
runTests
|
||||
# ---> C
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Object files
|
||||
*.o
|
||||
*.exe
|
||||
*.ko
|
||||
*.obj
|
||||
*.elf
|
||||
|
||||
# Linker output
|
||||
*.ilk
|
||||
*.map
|
||||
*.exp
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Libraries
|
||||
*.lib
|
||||
*.la
|
||||
*.lo
|
||||
|
||||
# Shared objects (inc. Windows DLLs)
|
||||
*.dll
|
||||
*.so
|
||||
*.so.*
|
||||
*.dylib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
*.i*86
|
||||
*.x86_64
|
||||
*.hex
|
||||
Startcode/mnist
|
||||
Startcode/runTests
|
||||
|
||||
# Debug files
|
||||
*.dSYM/
|
||||
*.su
|
||||
*.idb
|
||||
*.pdb
|
||||
|
||||
# Kernel Module Compile Results
|
||||
*.mod*
|
||||
*.cmd
|
||||
.tmp_versions/
|
||||
modules.order
|
||||
Module.symvers
|
||||
Mkfile.old
|
||||
dkms.conf
|
||||
|
||||
# IDE folders
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
@@ -0,0 +1,22 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "imageInput.h"
|
||||
|
||||
#define BUFFER_SIZE 100
|
||||
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
||||
|
||||
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
||||
|
||||
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
||||
GrayScaleImageSeries *readImages(const char *path)
|
||||
{
|
||||
GrayScaleImageSeries *series = NULL;
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
||||
void clearSeries(GrayScaleImageSeries *series)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#ifndef IMAGEINPUT_H
|
||||
#define IMAGEINPUT_H
|
||||
|
||||
typedef unsigned char GrayScalePixelType;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GrayScalePixelType *buffer;
|
||||
unsigned int width;
|
||||
unsigned int height;
|
||||
} GrayScaleImage;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GrayScaleImage *images;
|
||||
unsigned char *labels;
|
||||
unsigned int count;
|
||||
} GrayScaleImageSeries;
|
||||
|
||||
GrayScaleImageSeries *readImages(const char *path);
|
||||
void clearSeries(GrayScaleImageSeries *series);
|
||||
|
||||
#endif
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,35 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "matrix.h"
|
||||
|
||||
// TODO Matrix-Funktionen implementieren
|
||||
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void clearMatrix(Matrix *matrix)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -6,12 +6,7 @@
|
||||
typedef float MatrixType;
|
||||
|
||||
// TODO Matrixtyp definieren
|
||||
typedef struct{
|
||||
unsigned int rows;
|
||||
unsigned int cols;
|
||||
MatrixType* buffer;
|
||||
|
||||
} Matrix;
|
||||
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols);
|
||||
void clearMatrix(Matrix *matrix);
|
||||
File renamed without changes.
@@ -0,0 +1,173 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mnistVisualization.h"
|
||||
#include "raylib.h"
|
||||
|
||||
#define MAX_TEXT_LEN 100
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Vector2 position;
|
||||
const GrayScaleImageSeries *series;
|
||||
const unsigned char *predictions;
|
||||
unsigned int currentIdx;
|
||||
Vector2 pixelSize;
|
||||
} MnistVisualization;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char text[MAX_TEXT_LEN];
|
||||
Vector2 position;
|
||||
unsigned int fontSize;
|
||||
Color color;
|
||||
} TextLabel;
|
||||
|
||||
static TextLabel *createTextLabel(const char *text, unsigned int fontSize, Color color)
|
||||
{
|
||||
TextLabel *label = NULL;
|
||||
|
||||
if(text != NULL)
|
||||
{
|
||||
label = (TextLabel *)malloc(sizeof(TextLabel));
|
||||
|
||||
if(label != NULL)
|
||||
{
|
||||
strncpy(label->text, text, MAX_TEXT_LEN);
|
||||
label->text[MAX_TEXT_LEN-1] = '\0';
|
||||
label->position.x = 0;
|
||||
label->position.y = 0;
|
||||
label->fontSize = fontSize;
|
||||
label->color = color;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
static MnistVisualization *createVisualizationContainer(const GrayScaleImageSeries *series, const unsigned char predictions[], Vector2 size)
|
||||
{
|
||||
MnistVisualization *container = NULL;
|
||||
|
||||
if(size.x > 0 && size.y > 0 && series != NULL && series->images != NULL && series->count > 0 && predictions != NULL)
|
||||
{
|
||||
container = (MnistVisualization *)calloc(1, sizeof(MnistVisualization));
|
||||
|
||||
if(container != NULL)
|
||||
{
|
||||
Vector2 pixelSize = {(int)(size.x / series->images[0].width), (int)(size.y / series->images[0].height)};
|
||||
container->pixelSize = pixelSize;
|
||||
container->series = series;
|
||||
container->predictions = predictions;
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
static void drawTextLabel(const TextLabel *label)
|
||||
{
|
||||
DrawText(label->text, label->position.x, label->position.y, label->fontSize, label->color);
|
||||
}
|
||||
|
||||
static void drawDigit(const GrayScaleImage image, Vector2 position, Vector2 pixelSize)
|
||||
{
|
||||
float xOffset = 0;
|
||||
float yOffset = position.y - pixelSize.y;
|
||||
Color color = {0};
|
||||
color.a = 255;
|
||||
|
||||
for(int i = 0; i < image.width * image.height; i++)
|
||||
{
|
||||
if(i % image.width == 0)
|
||||
{
|
||||
xOffset = position.x;
|
||||
yOffset += pixelSize.y;
|
||||
}
|
||||
|
||||
color.r = image.buffer[i];
|
||||
color.b = image.buffer[i];
|
||||
color.g = image.buffer[i];
|
||||
|
||||
DrawRectangle(xOffset, yOffset, pixelSize.x, pixelSize.y, color);
|
||||
xOffset += pixelSize.x;
|
||||
}
|
||||
}
|
||||
|
||||
static void drawAll(const MnistVisualization *container, const TextLabel *navigationLabel, const TextLabel *predictionLabel)
|
||||
{
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
|
||||
drawDigit(container->series->images[container->currentIdx], container->position, container->pixelSize);
|
||||
drawTextLabel(navigationLabel);
|
||||
drawTextLabel(predictionLabel);
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
static int checkUserInput()
|
||||
{
|
||||
int inputResult = 0;
|
||||
|
||||
if(IsKeyReleased(KEY_LEFT))
|
||||
inputResult = -1;
|
||||
else if(IsKeyReleased(KEY_RIGHT))
|
||||
inputResult = 1;
|
||||
|
||||
return inputResult;
|
||||
}
|
||||
|
||||
static void updateDisplayContainer(MnistVisualization *container, int updateDirection)
|
||||
{
|
||||
int newIndex = (int)container->currentIdx + updateDirection;
|
||||
|
||||
if(newIndex < 0)
|
||||
newIndex = 0;
|
||||
else if(newIndex >= container->series->count)
|
||||
newIndex = container->series->count - 1;
|
||||
|
||||
container->currentIdx = newIndex;
|
||||
}
|
||||
|
||||
static void updatePredictionLabel(TextLabel *predictionLabel, unsigned char trueLabel, unsigned char predictedLabel)
|
||||
{
|
||||
snprintf(predictionLabel->text, MAX_TEXT_LEN, "True label: %u\nPredicted label: %u", trueLabel, predictedLabel);
|
||||
}
|
||||
|
||||
static void update(MnistVisualization *container, TextLabel *predictionLabel, int updateDirection)
|
||||
{
|
||||
updateDisplayContainer(container, updateDirection);
|
||||
updatePredictionLabel(predictionLabel, container->series->labels[container->currentIdx], container->predictions[container->currentIdx]);
|
||||
}
|
||||
|
||||
void showMnist(unsigned int windowWidth, unsigned int windowHeight, const GrayScaleImageSeries *series, const unsigned char predictions[])
|
||||
{
|
||||
const Vector2 windowSize = {windowWidth, windowHeight};
|
||||
|
||||
MnistVisualization *container = createVisualizationContainer(series, predictions, windowSize);
|
||||
TextLabel *navigationLabel = createTextLabel("Use left and right key to navigate ...", 20, WHITE);
|
||||
TextLabel *predictionLabel = createTextLabel("", 20, WHITE);
|
||||
predictionLabel->position.x = 10;
|
||||
predictionLabel->position.y = windowSize.y - 50;
|
||||
|
||||
if(container != NULL && navigationLabel != NULL && predictionLabel != NULL)
|
||||
{
|
||||
InitWindow(windowSize.x, windowSize.y, "MNIST Browser");
|
||||
|
||||
SetTargetFPS(60);
|
||||
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
int updateDirection = checkUserInput();
|
||||
update(container, predictionLabel, updateDirection);
|
||||
drawAll(container, navigationLabel, predictionLabel);
|
||||
}
|
||||
}
|
||||
|
||||
CloseWindow();
|
||||
|
||||
free(container);
|
||||
free(navigationLabel);
|
||||
free(predictionLabel);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -170,7 +170,7 @@ NeuralNetwork loadModel(const char *path)
|
||||
|
||||
static Matrix imageBatchToMatrixOfImageVectors(const GrayScaleImage images[], unsigned int count)
|
||||
{
|
||||
Matrix matrix = {0, 0, NULL}; //hier evtl Null auf int casten?
|
||||
Matrix matrix = {NULL, 0, 0};
|
||||
|
||||
if(count > 0 && images != NULL)
|
||||
{
|
||||
File renamed without changes.
@@ -8,42 +8,7 @@
|
||||
|
||||
static void prepareNeuralNetworkFile(const char *path, const NeuralNetwork nn)
|
||||
{
|
||||
FILE *file = fopen(path, "wb");
|
||||
|
||||
if (file == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Header schreiben
|
||||
const char *fileTag = "__info2_neural_network_file_format__";
|
||||
fwrite(fileTag, sizeof(char), strlen(fileTag), file);
|
||||
|
||||
// 2. Alle Schichten schreiben
|
||||
for (unsigned int i = 0; i < nn.numberOfLayers; i++) {
|
||||
// NUR bei der ERSTEN Schicht: Input-Dimension schreiben
|
||||
if (i == 0) {
|
||||
int inputDim = nn.layers[i].weights.cols;
|
||||
fwrite(&inputDim, sizeof(int), 1, file);
|
||||
}
|
||||
|
||||
// Output-Dimension (= Anzahl Zeilen der Gewichtsmatrix)
|
||||
int outputDim = nn.layers[i].weights.rows;
|
||||
fwrite(&outputDim, sizeof(int), 1, file);
|
||||
|
||||
// Gewichtsmatrix schreiben (alle Werte)
|
||||
int weightCount = nn.layers[i].weights.rows * nn.layers[i].weights.cols;
|
||||
fwrite(nn.layers[i].weights.buffer, sizeof(MatrixType), weightCount, file);
|
||||
|
||||
// Bias-Matrix schreiben (alle Werte)
|
||||
int biasCount = nn.layers[i].biases.rows * nn.layers[i].biases.cols;
|
||||
fwrite(nn.layers[i].biases.buffer, sizeof(MatrixType), biasCount, file);
|
||||
}
|
||||
|
||||
// 3. Terminator schreiben (outputDimension = 0 zum Stoppen)
|
||||
int terminator = 0;
|
||||
fwrite(&terminator, sizeof(int), 1, file);
|
||||
|
||||
fclose(file);
|
||||
// TODO
|
||||
}
|
||||
|
||||
void test_loadModelReturnsCorrectNumberOfLayers(void)
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
-180
@@ -1,180 +0,0 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "imageInput.h"
|
||||
|
||||
#define BUFFER_SIZE 100
|
||||
#define FILE_HEADER_STRING "__info2_image_file_format__"
|
||||
|
||||
// TODO Implementieren Sie geeignete Hilfsfunktionen für das Lesen der Bildserie aus einer Datei
|
||||
|
||||
// TODO Vervollständigen Sie die Funktion readImages unter Benutzung Ihrer Hilfsfunktionen
|
||||
|
||||
|
||||
static int checkFileHeader(FILE *file)
|
||||
{
|
||||
char buffer[BUFFER_SIZE];
|
||||
int length = strlen(FILE_HEADER_STRING);
|
||||
|
||||
// Prüfen ob fread erfolgreich war
|
||||
if (fread(buffer, sizeof(char), length, file) != length) {
|
||||
return 0; // Lesefehler
|
||||
}
|
||||
|
||||
buffer[length] = '\0';
|
||||
|
||||
if (strcmp(buffer, FILE_HEADER_STRING) == 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int readDimensions(FILE *file, unsigned short * count, unsigned short *width, unsigned short *height)
|
||||
{
|
||||
// Anzahl lesen
|
||||
if (fread(count, sizeof(unsigned short), 1, file) != 1) {
|
||||
return 0;
|
||||
}
|
||||
// Breite lesen
|
||||
if (fread(width, sizeof(unsigned short), 1, file) != 1) {
|
||||
return 0;
|
||||
}
|
||||
// Höhe lesen
|
||||
if (fread(height, sizeof(unsigned short), 1, file) != 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1; // Alles ok
|
||||
}
|
||||
|
||||
static int readSingleImage(FILE *file, GrayScaleImage *image, unsigned char *label, unsigned short width, unsigned short height)
|
||||
{
|
||||
// Schritt 1: Gesamtzahl Pixel berechnen
|
||||
int totalPixels = width * height;
|
||||
|
||||
// Schritt 2: Speicher allokieren
|
||||
image->buffer = (unsigned char *)malloc(totalPixels * sizeof(unsigned char));
|
||||
if (image->buffer == NULL) {
|
||||
return 0; // Fehler: kein Speicher verfügbar
|
||||
}
|
||||
|
||||
// Schritt 3: Breite und Höhe setzen
|
||||
image->width = width;
|
||||
image->height = height;
|
||||
|
||||
// Schritt 4: Pixel lesen
|
||||
if (fread(image->buffer, sizeof(unsigned char), totalPixels, file) != totalPixels) {
|
||||
free(image->buffer); // Aufräumen!
|
||||
return 0; // Fehler beim Lesen
|
||||
}
|
||||
|
||||
// Schritt 5: Label lesen
|
||||
if (fread(label, sizeof(unsigned char), 1, file) != 1) {
|
||||
free(image->buffer); // Aufräumen!
|
||||
return 0; // Fehler beim Lesen
|
||||
}
|
||||
|
||||
return 1; // Erfolg!
|
||||
}
|
||||
|
||||
GrayScaleImageSeries *readImages(const char *path)
|
||||
{
|
||||
// Schritt 1: Datei öffnen
|
||||
FILE *file = fopen(path, "rb");
|
||||
if (file == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 2: Header prüfen
|
||||
if (!checkFileHeader(file)) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 3: Dimensionen lesen
|
||||
unsigned short count, width, height;
|
||||
if (!readDimensions(file, &count, &width, &height)) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 4: Speicher für die Serie allokieren
|
||||
GrayScaleImageSeries *series = (GrayScaleImageSeries *)malloc(sizeof(GrayScaleImageSeries));
|
||||
if (series == NULL) {
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 5: Speicher für das images-Array allokieren
|
||||
series->images = (GrayScaleImage *)malloc(count * sizeof(GrayScaleImage));
|
||||
if (series->images == NULL) {
|
||||
free(series);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 6: Speicher für das labels-Array allokieren
|
||||
series->labels = (unsigned char *)malloc(count * sizeof(unsigned char));
|
||||
if (series->labels == NULL) {
|
||||
free(series->images);
|
||||
free(series);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Schritt 7: count setzen
|
||||
series->count = count;
|
||||
|
||||
// Schritt 8: Alle Bilder in einer Schleife einlesen
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (!readSingleImage(file, &series->images[i], &series->labels[i], width, height)) {
|
||||
// Bei Fehler: Aufräumen!
|
||||
for (int j = 0; j < i; j++) {
|
||||
free(series->images[j].buffer);
|
||||
}
|
||||
free(series->images);
|
||||
free(series->labels);
|
||||
free(series);
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Schritt 9: Datei schließen
|
||||
fclose(file);
|
||||
|
||||
// Schritt 10: Fertige Serie zurückgeben
|
||||
return series;
|
||||
}
|
||||
|
||||
// TODO Vervollständigen Sie die Funktion clearSeries, welche eine Bildserie vollständig aus dem Speicher freigibt
|
||||
void clearSeries(GrayScaleImageSeries *series)
|
||||
{
|
||||
// Schritt 0: Prüfen ob series überhaupt existiert
|
||||
if (series == NULL) {
|
||||
return; // Nichts zu tun
|
||||
}
|
||||
|
||||
// Schritt 1: Alle Pixel-Buffer freigeben (für jedes Bild)
|
||||
if (series->images != NULL) {
|
||||
for (int i = 0; i < series->count; i++) {
|
||||
if (series->images[i].buffer != NULL) {
|
||||
free(series->images[i].buffer); // ← Buffer von Bild i freigeben
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Schritt 2: Das images-Array freigeben
|
||||
if (series->images != NULL) {
|
||||
free(series->images);
|
||||
}
|
||||
|
||||
// Schritt 3: Das labels-Array freigeben
|
||||
if (series->labels != NULL) {
|
||||
free(series->labels);
|
||||
}
|
||||
|
||||
// Schritt 4: Die Serie-Struktur selbst freigeben
|
||||
free(series);
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
#ifndef IMAGEINPUT_H
|
||||
#define IMAGEINPUT_H
|
||||
|
||||
typedef unsigned char GrayScalePixelType;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GrayScalePixelType *buffer; // Breite in Pixeln
|
||||
unsigned int width; // Höhe in Pixeln
|
||||
unsigned int height; // Die Pixelwerte (0-255)
|
||||
} GrayScaleImage; // EIN Bild
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GrayScaleImage *images; // Array von Bildern
|
||||
unsigned char *labels; // Array von Labels (welche Ziffer?)
|
||||
unsigned int count; // Wie viele Bilder ?
|
||||
} GrayScaleImageSeries; // Sammlung der Bilder
|
||||
|
||||
GrayScaleImageSeries *readImages(const char *path);
|
||||
void clearSeries(GrayScaleImageSeries *series);
|
||||
|
||||
#endif
|
||||
@@ -1,173 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "matrix.h"
|
||||
|
||||
// TODO Matrix-Funktionen implementieren
|
||||
|
||||
Matrix createMatrix(unsigned int rows, unsigned int cols)
|
||||
{
|
||||
Matrix m;
|
||||
m.rows = rows;
|
||||
m.cols = cols;
|
||||
|
||||
m.buffer = (MatrixType*)malloc(sizeof(MatrixType) * rows * cols);
|
||||
// Prüfe auf ungültige Dimensionen
|
||||
if (rows == 0 || cols == 0) {
|
||||
m.rows = 0;
|
||||
m.cols = 0;
|
||||
m.buffer = NULL;
|
||||
return m;
|
||||
}
|
||||
if (m.buffer == NULL){
|
||||
fprintf(stderr, "Error: Memory allocation failed in createMatrix!.\n");
|
||||
m.rows = 0;
|
||||
m.cols = 0;
|
||||
return m;
|
||||
}
|
||||
for (unsigned int i = 0; i < rows * cols; i++){
|
||||
m.buffer[i] = 0.0f;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
|
||||
void clearMatrix(Matrix *matrix)
|
||||
{
|
||||
|
||||
if (matrix == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Speicher freigeben falls vorhanden
|
||||
if (matrix->buffer != NULL) {
|
||||
free(matrix->buffer);
|
||||
matrix->buffer = NULL;
|
||||
}
|
||||
|
||||
// Dimensionen zurücksetzen
|
||||
matrix->rows = 0;
|
||||
matrix->cols = 0;
|
||||
|
||||
}
|
||||
|
||||
void setMatrixAt(MatrixType value, Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
if (rowIdx >= matrix.rows || colIdx >= matrix.cols) {
|
||||
fprintf(stderr, "Error: setMatrixAt index out of bounds.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
matrix.buffer[rowIdx * matrix.cols + colIdx] = value;
|
||||
}
|
||||
|
||||
MatrixType getMatrixAt(const Matrix matrix, unsigned int rowIdx, unsigned int colIdx)
|
||||
{
|
||||
if (rowIdx >= matrix.rows || colIdx >= matrix.cols) {
|
||||
fprintf(stderr, "Error: getMatrixAt index out of bounds.\n");
|
||||
return UNDEFINED_MATRIX_VALUE;
|
||||
}
|
||||
|
||||
return matrix.buffer[rowIdx * matrix.cols + colIdx];
|
||||
}
|
||||
|
||||
Matrix add(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
Matrix result;
|
||||
|
||||
// Fall 1: Normale elementweise Addition (gleiche Dimensionen)
|
||||
if (matrix1.rows == matrix2.rows && matrix1.cols == matrix2.cols) {
|
||||
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||
if (result.buffer == NULL) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < matrix1.rows; i++) {
|
||||
for (unsigned int j = 0; j < matrix1.cols; j++) {
|
||||
setMatrixAt(
|
||||
getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, j),
|
||||
result,
|
||||
i, j
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fall 2: Broadcasting - matrix2 ist Spaltenvektor (cols=1)
|
||||
else if (matrix1.rows == matrix2.rows && matrix2.cols == 1) {
|
||||
result = createMatrix(matrix1.rows, matrix1.cols);
|
||||
if (result.buffer == NULL) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < matrix1.rows; i++) {
|
||||
for (unsigned int j = 0; j < matrix1.cols; j++) {
|
||||
// matrix2 hat nur 1 Spalte (Index 0), wird über alle Spalten verteilt
|
||||
setMatrixAt(
|
||||
getMatrixAt(matrix1, i, j) + getMatrixAt(matrix2, i, 0),
|
||||
result,
|
||||
i, j
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fall 3: Broadcasting - matrix1 ist Spaltenvektor (cols=1)
|
||||
else if (matrix2.rows == matrix1.rows && matrix1.cols == 1) {
|
||||
result = createMatrix(matrix2.rows, matrix2.cols);
|
||||
if (result.buffer == NULL) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < matrix2.rows; i++) {
|
||||
for (unsigned int j = 0; j < matrix2.cols; j++) {
|
||||
// matrix1 hat nur 1 Spalte (Index 0), wird über alle Spalten verteilt
|
||||
setMatrixAt(
|
||||
getMatrixAt(matrix1, i, 0) + getMatrixAt(matrix2, i, j),
|
||||
result,
|
||||
i, j
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Fall 4: Ungültige Dimensionen
|
||||
else {
|
||||
fprintf(stderr, "Error: Matrix dimensions do not match for addition.\n");
|
||||
Matrix empty = {0, 0, NULL};
|
||||
return empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Matrix multiply(const Matrix matrix1, const Matrix matrix2)
|
||||
{
|
||||
if (matrix1.cols != matrix2.rows) {
|
||||
fprintf(stderr, "Error: Invalid matrix dimensions for multiplication.\n");
|
||||
Matrix empty = {0, 0, NULL};
|
||||
return empty;
|
||||
}
|
||||
|
||||
Matrix result = createMatrix(matrix1.rows, matrix2.cols);
|
||||
if (result.buffer == NULL) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < matrix1.rows; i++) {
|
||||
for (unsigned int j = 0; j < matrix2.cols; j++) {
|
||||
MatrixType sum = 0.0f;
|
||||
|
||||
for (unsigned int k = 0; k < matrix1.cols; k++) {
|
||||
sum += getMatrixAt(matrix1, i, k) * getMatrixAt(matrix2, k, j);
|
||||
}
|
||||
|
||||
setMatrixAt(sum, result, i, j);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -1,570 +0,0 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "mnistVisualization.h"
|
||||
#include "imageInput.h"
|
||||
#include "neuralNetwork.h"
|
||||
|
||||
// Matrix-Namenskonflikt mit Raylib lösen: Raylib's Matrix umbenennen
|
||||
#define Matrix RaylibMatrix
|
||||
#include "raylib.h"
|
||||
#undef Matrix
|
||||
|
||||
#define MAX_TEXT_LEN 100
|
||||
|
||||
// Enum für die verschiedenen Modi
|
||||
typedef enum {
|
||||
MODE_BROWSE,
|
||||
MODE_DRAW
|
||||
} AppMode;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Vector2 position;
|
||||
const GrayScaleImageSeries *series;
|
||||
const unsigned char *predictions;
|
||||
unsigned int currentIdx;
|
||||
Vector2 pixelSize;
|
||||
AppMode mode;
|
||||
GrayScaleImage drawingCanvas;
|
||||
unsigned char canvasPrediction;
|
||||
const NeuralNetwork *model;
|
||||
} MnistVisualization;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char text[MAX_TEXT_LEN];
|
||||
Vector2 position;
|
||||
unsigned int fontSize;
|
||||
Color color;
|
||||
} TextLabel;
|
||||
|
||||
static TextLabel *createTextLabel(const char *text, unsigned int fontSize, Color color)
|
||||
{
|
||||
TextLabel *label = NULL;
|
||||
|
||||
if(text != NULL)
|
||||
{
|
||||
label = (TextLabel *)malloc(sizeof(TextLabel));
|
||||
|
||||
if(label != NULL)
|
||||
{
|
||||
strncpy(label->text, text, MAX_TEXT_LEN);
|
||||
label->text[MAX_TEXT_LEN-1] = '\0';
|
||||
label->position.x = 0;
|
||||
label->position.y = 0;
|
||||
label->fontSize = fontSize;
|
||||
label->color = color;
|
||||
}
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
static GrayScaleImage createCanvas(unsigned int width, unsigned int height)
|
||||
{
|
||||
GrayScaleImage canvas;
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
canvas.buffer = (GrayScalePixelType *)calloc(width * height, sizeof(GrayScalePixelType));
|
||||
return canvas;
|
||||
}
|
||||
|
||||
static void clearCanvas(GrayScaleImage *canvas)
|
||||
{
|
||||
if(canvas != NULL && canvas->buffer != NULL)
|
||||
{
|
||||
memset(canvas->buffer, 0, canvas->width * canvas->height * sizeof(GrayScalePixelType));
|
||||
}
|
||||
}
|
||||
|
||||
static GrayScaleImage downsampleCanvas(const GrayScaleImage *largeCanvas, unsigned int targetWidth, unsigned int targetHeight)
|
||||
{
|
||||
GrayScaleImage smallImage = createCanvas(targetWidth, targetHeight);
|
||||
|
||||
if(smallImage.buffer != NULL && largeCanvas != NULL && largeCanvas->buffer != NULL)
|
||||
{
|
||||
unsigned int scaleX = largeCanvas->width / targetWidth;
|
||||
unsigned int scaleY = largeCanvas->height / targetHeight;
|
||||
|
||||
for(unsigned int y = 0; y < targetHeight; y++)
|
||||
{
|
||||
for(unsigned int x = 0; x < targetWidth; x++)
|
||||
{
|
||||
// Durchschnitt über den entsprechenden Bereich berechnen
|
||||
unsigned int sum = 0;
|
||||
unsigned int count = 0;
|
||||
|
||||
for(unsigned int sy = 0; sy < scaleY; sy++)
|
||||
{
|
||||
for(unsigned int sx = 0; sx < scaleX; sx++)
|
||||
{
|
||||
unsigned int srcX = x * scaleX + sx;
|
||||
unsigned int srcY = y * scaleY + sy;
|
||||
|
||||
if(srcX < largeCanvas->width && srcY < largeCanvas->height)
|
||||
{
|
||||
sum += largeCanvas->buffer[srcY * largeCanvas->width + srcX];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
smallImage.buffer[y * targetWidth + x] = (unsigned char)(sum / count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return smallImage;
|
||||
}
|
||||
|
||||
static MnistVisualization *createVisualizationContainer(const GrayScaleImageSeries *series, const unsigned char predictions[], Vector2 size, const NeuralNetwork *model)
|
||||
{
|
||||
MnistVisualization *container = NULL;
|
||||
|
||||
if(size.x > 0 && size.y > 0 && series != NULL && series->images != NULL && series->count > 0 && predictions != NULL)
|
||||
{
|
||||
container = (MnistVisualization *)calloc(1, sizeof(MnistVisualization));
|
||||
|
||||
if(container != NULL)
|
||||
{
|
||||
// Canvas ist 4x größer (112x112), also pixelSize anpassen
|
||||
Vector2 pixelSize = {(int)(size.x / (series->images[0].width * 4)),
|
||||
(int)(size.y / (series->images[0].height * 4))};
|
||||
container->pixelSize = pixelSize;
|
||||
container->series = series;
|
||||
container->predictions = predictions;
|
||||
container->mode = MODE_BROWSE;
|
||||
container->model = model;
|
||||
|
||||
// Canvas erstellen (4x größer als MNIST für feineres Zeichnen: 112x112 statt 28x28)
|
||||
unsigned int canvasSize = series->images[0].width * 4;
|
||||
container->drawingCanvas = createCanvas(canvasSize, canvasSize);
|
||||
container->canvasPrediction = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
static void drawTextLabel(const TextLabel *label)
|
||||
{
|
||||
DrawText(label->text, label->position.x, label->position.y, label->fontSize, label->color);
|
||||
}
|
||||
|
||||
static void drawDigit(const GrayScaleImage image, Vector2 position, Vector2 pixelSize)
|
||||
{
|
||||
float xOffset = 0;
|
||||
float yOffset = position.y - pixelSize.y;
|
||||
Color color = {0};
|
||||
color.a = 255;
|
||||
|
||||
for(int i = 0; i < image.width * image.height; i++)
|
||||
{
|
||||
if(i % image.width == 0)
|
||||
{
|
||||
xOffset = position.x;
|
||||
yOffset += pixelSize.y;
|
||||
}
|
||||
|
||||
color.r = image.buffer[i];
|
||||
color.b = image.buffer[i];
|
||||
color.g = image.buffer[i];
|
||||
|
||||
DrawRectangle(xOffset, yOffset, pixelSize.x, pixelSize.y, color);
|
||||
xOffset += pixelSize.x;
|
||||
}
|
||||
}
|
||||
|
||||
static void drawAll(const MnistVisualization *container, const TextLabel *navigationLabel, const TextLabel *predictionLabel, const TextLabel *modeLabel)
|
||||
{
|
||||
BeginDrawing();
|
||||
ClearBackground(BLACK);
|
||||
|
||||
if(container->mode == MODE_BROWSE)
|
||||
{
|
||||
// Im Browse-Modus: MNIST Bilder sind 28x28, aber pixelSize ist für 112x112
|
||||
// Also pixelSize * 4 verwenden
|
||||
Vector2 browsePixelSize = {container->pixelSize.x * 4, container->pixelSize.y * 4};
|
||||
drawDigit(container->series->images[container->currentIdx], container->position, browsePixelSize);
|
||||
}
|
||||
else // MODE_DRAW
|
||||
{
|
||||
// Im Draw-Modus: Canvas ist 112x112, pixelSize passt
|
||||
drawDigit(container->drawingCanvas, container->position, container->pixelSize);
|
||||
}
|
||||
|
||||
drawTextLabel(navigationLabel);
|
||||
drawTextLabel(predictionLabel);
|
||||
drawTextLabel(modeLabel);
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
static int checkUserInput()
|
||||
{
|
||||
int inputResult = 0;
|
||||
|
||||
if(IsKeyReleased(KEY_LEFT))
|
||||
inputResult = -1;
|
||||
else if(IsKeyReleased(KEY_RIGHT))
|
||||
inputResult = 1;
|
||||
|
||||
return inputResult;
|
||||
}
|
||||
|
||||
static void handleDrawing(MnistVisualization *container)
|
||||
{
|
||||
static Vector2 lastMousePos = {-1, -1};
|
||||
|
||||
if(IsMouseButtonDown(MOUSE_LEFT_BUTTON))
|
||||
{
|
||||
Vector2 mousePos = GetMousePosition();
|
||||
|
||||
// Berechne welches Pixel geklickt wurde (pixelSize ist bereits für 112x112)
|
||||
int pixelX = (int)((mousePos.x - container->position.x) / container->pixelSize.x);
|
||||
int pixelY = (int)((mousePos.y - container->position.y) / container->pixelSize.y);
|
||||
|
||||
// Wenn wir eine vorherige Position haben, zeichne eine Linie
|
||||
if(lastMousePos.x >= 0 && lastMousePos.y >= 0)
|
||||
{
|
||||
int lastPixelX = (int)((lastMousePos.x - container->position.x) / container->pixelSize.x);
|
||||
int lastPixelY = (int)((lastMousePos.y - container->position.y) / container->pixelSize.y);
|
||||
|
||||
// Bresenham Linien-Algorithmus (vereinfacht)
|
||||
int dx = abs(pixelX - lastPixelX);
|
||||
int dy = abs(pixelY - lastPixelY);
|
||||
int sx = (lastPixelX < pixelX) ? 1 : -1;
|
||||
int sy = (lastPixelY < pixelY) ? 1 : -1;
|
||||
int err = dx - dy;
|
||||
|
||||
int currentX = lastPixelX;
|
||||
int currentY = lastPixelY;
|
||||
|
||||
while(1)
|
||||
{
|
||||
// Zeichne dünnen Pinsel für 112x112 Canvas
|
||||
if(currentX >= 0 && currentX < container->drawingCanvas.width &&
|
||||
currentY >= 0 && currentY < container->drawingCanvas.height)
|
||||
{
|
||||
// Zentrum: 2x2 Pixel weiß (entspricht 0.5x0.5 auf 28x28)
|
||||
for(int dy = 0; dy <= 1; dy++)
|
||||
{
|
||||
for(int dx = 0; dx <= 1; dx++)
|
||||
{
|
||||
int nx = currentX + dx;
|
||||
int ny = currentY + dy;
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
container->drawingCanvas.buffer[nidx] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ring 1: Direkte Nachbarn (sehr hell)
|
||||
int ring1[][2] = {{-1,0}, {-1,1}, {0,-1}, {2,0}, {2,1}, {0,2}, {1,2}, {1,-1}};
|
||||
for(int i = 0; i < 8; i++)
|
||||
{
|
||||
int nx = currentX + ring1[i][0];
|
||||
int ny = currentY + ring1[i][1];
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
if(container->drawingCanvas.buffer[nidx] < 200) {
|
||||
container->drawingCanvas.buffer[nidx] = 200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ring 2: Weitere Nachbarn (mittel)
|
||||
int ring2[][2] = {{-2,0}, {-2,1}, {-1,-1}, {-1,2}, {0,-2}, {0,3},
|
||||
{1,-2}, {1,3}, {2,-1}, {2,2}, {3,0}, {3,1}};
|
||||
for(int i = 0; i < 12; i++)
|
||||
{
|
||||
int nx = currentX + ring2[i][0];
|
||||
int ny = currentY + ring2[i][1];
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
if(container->drawingCanvas.buffer[nidx] < 140) {
|
||||
container->drawingCanvas.buffer[nidx] = 140;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ring 3: Äußere Nachbarn (dunkel)
|
||||
int ring3[][2] = {{-3,0}, {-3,1}, {-2,-1}, {-2,2}, {-1,-2}, {-1,3},
|
||||
{0,-3}, {0,4}, {1,-3}, {1,4}, {2,-2}, {2,3},
|
||||
{3,-1}, {3,2}, {4,0}, {4,1}};
|
||||
for(int i = 0; i < 16; i++)
|
||||
{
|
||||
int nx = currentX + ring3[i][0];
|
||||
int ny = currentY + ring3[i][1];
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
if(container->drawingCanvas.buffer[nidx] < 80) {
|
||||
container->drawingCanvas.buffer[nidx] = 80;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(currentX == pixelX && currentY == pixelY) break;
|
||||
|
||||
int e2 = 2 * err;
|
||||
if(e2 > -dy)
|
||||
{
|
||||
err -= dy;
|
||||
currentX += sx;
|
||||
}
|
||||
if(e2 < dx)
|
||||
{
|
||||
err += dx;
|
||||
currentY += sy;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Erstes Pixel (kein Vorgänger)
|
||||
if(pixelX >= 0 && pixelX < container->drawingCanvas.width &&
|
||||
pixelY >= 0 && pixelY < container->drawingCanvas.height)
|
||||
{
|
||||
// Gleiche Logik wie oben
|
||||
for(int dy = 0; dy <= 1; dy++)
|
||||
{
|
||||
for(int dx = 0; dx <= 1; dx++)
|
||||
{
|
||||
int nx = pixelX + dx;
|
||||
int ny = pixelY + dy;
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
container->drawingCanvas.buffer[nidx] = 255;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ring1[][2] = {{-1,0}, {-1,1}, {0,-1}, {2,0}, {2,1}, {0,2}, {1,2}, {1,-1}};
|
||||
for(int i = 0; i < 8; i++)
|
||||
{
|
||||
int nx = pixelX + ring1[i][0];
|
||||
int ny = pixelY + ring1[i][1];
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
if(container->drawingCanvas.buffer[nidx] < 200) {
|
||||
container->drawingCanvas.buffer[nidx] = 200;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ring2[][2] = {{-2,0}, {-2,1}, {-1,-1}, {-1,2}, {0,-2}, {0,3},
|
||||
{1,-2}, {1,3}, {2,-1}, {2,2}, {3,0}, {3,1}};
|
||||
for(int i = 0; i < 12; i++)
|
||||
{
|
||||
int nx = pixelX + ring2[i][0];
|
||||
int ny = pixelY + ring2[i][1];
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
if(container->drawingCanvas.buffer[nidx] < 140) {
|
||||
container->drawingCanvas.buffer[nidx] = 140;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ring3[][2] = {{-3,0}, {-3,1}, {-2,-1}, {-2,2}, {-1,-2}, {-1,3},
|
||||
{0,-3}, {0,4}, {1,-3}, {1,4}, {2,-2}, {2,3},
|
||||
{3,-1}, {3,2}, {4,0}, {4,1}};
|
||||
for(int i = 0; i < 16; i++)
|
||||
{
|
||||
int nx = pixelX + ring3[i][0];
|
||||
int ny = pixelY + ring3[i][1];
|
||||
if(nx >= 0 && nx < container->drawingCanvas.width &&
|
||||
ny >= 0 && ny < container->drawingCanvas.height)
|
||||
{
|
||||
int nidx = ny * container->drawingCanvas.width + nx;
|
||||
if(container->drawingCanvas.buffer[nidx] < 80) {
|
||||
container->drawingCanvas.buffer[nidx] = 80;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastMousePos = mousePos;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Maustaste losgelassen - Reset der letzten Position
|
||||
lastMousePos.x = -1;
|
||||
lastMousePos.y = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static void updatePredictionForCanvas(MnistVisualization *container)
|
||||
{
|
||||
if(container->model != NULL && container->series != NULL && container->series->images != NULL)
|
||||
{
|
||||
// Canvas von 112x112 auf 28x28 runterskalieren
|
||||
unsigned int targetSize = container->series->images[0].width;
|
||||
GrayScaleImage downsampled = downsampleCanvas(&container->drawingCanvas, targetSize, targetSize);
|
||||
|
||||
if(downsampled.buffer != NULL)
|
||||
{
|
||||
unsigned char *prediction = predict(*container->model, &downsampled, 1);
|
||||
if(prediction != NULL)
|
||||
{
|
||||
container->canvasPrediction = prediction[0];
|
||||
free(prediction);
|
||||
}
|
||||
|
||||
// Downsampled Image aufräumen
|
||||
free(downsampled.buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void updateDisplayContainer(MnistVisualization *container, int updateDirection)
|
||||
{
|
||||
int newIndex = (int)container->currentIdx + updateDirection;
|
||||
|
||||
if(newIndex < 0)
|
||||
newIndex = 0;
|
||||
else if(newIndex >= container->series->count)
|
||||
newIndex = container->series->count - 1;
|
||||
|
||||
container->currentIdx = newIndex;
|
||||
}
|
||||
|
||||
static void updatePredictionLabel(TextLabel *predictionLabel, unsigned char trueLabel, unsigned char predictedLabel, AppMode mode)
|
||||
{
|
||||
if(mode == MODE_BROWSE)
|
||||
{
|
||||
snprintf(predictionLabel->text, MAX_TEXT_LEN, "True label: %u\nPredicted label: %u", trueLabel, predictedLabel);
|
||||
}
|
||||
else // MODE_DRAW
|
||||
{
|
||||
snprintf(predictionLabel->text, MAX_TEXT_LEN, "Predicted label: %u", predictedLabel);
|
||||
}
|
||||
}
|
||||
|
||||
static void updateModeLabel(TextLabel *modeLabel, AppMode mode)
|
||||
{
|
||||
if(mode == MODE_BROWSE)
|
||||
{
|
||||
snprintf(modeLabel->text, MAX_TEXT_LEN, "Mode: BROWSE | Press 'D' to draw");
|
||||
}
|
||||
else // MODE_DRAW
|
||||
{
|
||||
snprintf(modeLabel->text, MAX_TEXT_LEN, "Mode: DRAW | Press 'B' to browse | Press 'C' to clear");
|
||||
}
|
||||
}
|
||||
|
||||
static void update(MnistVisualization *container, TextLabel *predictionLabel, TextLabel *modeLabel, int updateDirection)
|
||||
{
|
||||
// Mode-Wechsel
|
||||
if(IsKeyPressed(KEY_D))
|
||||
{
|
||||
container->mode = MODE_DRAW;
|
||||
clearCanvas(&container->drawingCanvas);
|
||||
}
|
||||
else if(IsKeyPressed(KEY_B))
|
||||
{
|
||||
container->mode = MODE_BROWSE;
|
||||
}
|
||||
|
||||
// Canvas löschen im Draw-Mode
|
||||
if(container->mode == MODE_DRAW && IsKeyPressed(KEY_C))
|
||||
{
|
||||
clearCanvas(&container->drawingCanvas);
|
||||
container->canvasPrediction = 0;
|
||||
}
|
||||
|
||||
if(container->mode == MODE_BROWSE)
|
||||
{
|
||||
updateDisplayContainer(container, updateDirection);
|
||||
updatePredictionLabel(predictionLabel,
|
||||
container->series->labels[container->currentIdx],
|
||||
container->predictions[container->currentIdx],
|
||||
MODE_BROWSE);
|
||||
}
|
||||
else // MODE_DRAW
|
||||
{
|
||||
handleDrawing(container);
|
||||
|
||||
// Prediction alle paar Frames aktualisieren (nicht bei jedem Frame für Performance)
|
||||
static int frameCounter = 0;
|
||||
frameCounter++;
|
||||
if(frameCounter % 10 == 0)
|
||||
{
|
||||
updatePredictionForCanvas(container);
|
||||
}
|
||||
|
||||
updatePredictionLabel(predictionLabel, 0, container->canvasPrediction, MODE_DRAW);
|
||||
}
|
||||
|
||||
updateModeLabel(modeLabel, container->mode);
|
||||
}
|
||||
|
||||
void showMnist(unsigned int windowWidth, unsigned int windowHeight, const GrayScaleImageSeries *series, const unsigned char predictions[])
|
||||
{
|
||||
// Model laden (für Draw-Modus)
|
||||
NeuralNetwork model = loadModel("mnist_model.info2");
|
||||
|
||||
const Vector2 windowSize = {windowWidth, windowHeight};
|
||||
|
||||
MnistVisualization *container = createVisualizationContainer(series, predictions, windowSize, &model);
|
||||
TextLabel *navigationLabel = createTextLabel("Use left and right key to navigate ...", 20, WHITE);
|
||||
TextLabel *predictionLabel = createTextLabel("", 20, WHITE);
|
||||
TextLabel *modeLabel = createTextLabel("", 20, WHITE);
|
||||
|
||||
navigationLabel->position.x = windowSize.x - 400; // Rechts (mit Abstand)
|
||||
navigationLabel->position.y = windowSize.y - 30; // Ganz unten
|
||||
|
||||
predictionLabel->position.x = 10;
|
||||
predictionLabel->position.y = windowSize.y - 50;
|
||||
|
||||
modeLabel->position.x = 10;
|
||||
modeLabel->position.y = 10;
|
||||
|
||||
if(container != NULL && navigationLabel != NULL && predictionLabel != NULL && modeLabel != NULL)
|
||||
{
|
||||
InitWindow(windowSize.x, windowSize.y, "MNIST Browser & Drawer");
|
||||
|
||||
SetTargetFPS(60);
|
||||
|
||||
while (!WindowShouldClose())
|
||||
{
|
||||
int updateDirection = checkUserInput();
|
||||
update(container, predictionLabel, modeLabel, updateDirection);
|
||||
drawAll(container, navigationLabel, predictionLabel, modeLabel);
|
||||
}
|
||||
}
|
||||
|
||||
CloseWindow();
|
||||
|
||||
// Cleanup
|
||||
if(container != NULL)
|
||||
{
|
||||
if(container->drawingCanvas.buffer != NULL)
|
||||
{
|
||||
free(container->drawingCanvas.buffer);
|
||||
}
|
||||
free(container);
|
||||
}
|
||||
free(navigationLabel);
|
||||
free(predictionLabel);
|
||||
free(modeLabel);
|
||||
clearModel(&model);
|
||||
}
|
||||
Reference in new issue
Block a user