Compare commits

..
2 Commits
Author SHA1 Message Date
Anja Freudenreich 447e77a7bf updating 2026-09-17 14:46:11 +02:00
Anja Freudenreich a6749f07d1 updating 2026-09-17 14:45:27 +02:00
40 changed files with 315 additions and 991 deletions

No files matched your search

+60 -3
View File
@@ -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
+22
View File
@@ -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)
{
}
+23
View File
@@ -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.
View File
File renamed without changes.
View File
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+35
View File
@@ -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)
{
}
-5
View File
@@ -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.
+173
View File
@@ -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
View File
@@ -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);
}
-23
View File
@@ -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
-173
View File
@@ -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;
}
-570
View File
@@ -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);
}