Vecrist commited on
Commit
6292977
·
verified ·
1 Parent(s): ed08926

Upload handdrawnDigitClassification.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. handdrawnDigitClassification.py +136 -0
handdrawnDigitClassification.py ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import tkinter as tk
2
+ import torch
3
+ import torch.nn as nn
4
+ from torchvision import transforms
5
+ from PIL import Image, ImageDraw
6
+ from pathlib import Path
7
+ import torch
8
+
9
+
10
+ class CNN_MNIST(nn.Module):
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.convolutional_block = nn.Sequential(
14
+ nn.Conv2d(1, 32, kernel_size=3, padding=1), #28+1*2-3+1 = 28x28
15
+ nn.BatchNorm2d(32),
16
+ nn.ReLU(),
17
+ nn.MaxPool2d(2), #14x14
18
+
19
+ nn.Conv2d(32, 64, kernel_size=3, padding=1), #14x14
20
+ nn.BatchNorm2d(64),
21
+ nn.ReLU(),
22
+ nn.MaxPool2d(2), #7x7
23
+ nn.Dropout2d(0.25) #prevents overfitting
24
+ )
25
+
26
+ self.classifier = nn.Sequential(
27
+ nn.Flatten(),
28
+ nn.Linear(64 * 7 * 7, 128),
29
+ nn.ReLU(),
30
+ nn.Dropout(0.5),
31
+ nn.Linear(128, 10)
32
+ )
33
+
34
+ def forward(self, x):
35
+ x = self.convolutional_block(x)
36
+ x = self.classifier(x)
37
+ return x
38
+
39
+ model = CNN_MNIST()
40
+
41
+
42
+ BASE_DIR = Path(__file__).resolve().parent
43
+ WEIGHTS_PATH = BASE_DIR / "MNIST_CNNmodel_weights.pth"
44
+ try:
45
+ weights = torch.load(WEIGHTS_PATH, weights_only=True)
46
+ model.load_state_dict(weights)
47
+ print("Model weights loaded successfully")
48
+ except FileNotFoundError:
49
+ print(f"Error: '{WEIGHTS_PATH.name}' not found at {WEIGHTS_PATH}")
50
+
51
+
52
+ model.eval()
53
+
54
+
55
+ class MNISTDrawer:
56
+ def __init__(self, root):
57
+ self.root = root
58
+ self.root.title("MNIST Digit Predictor")
59
+
60
+ self.last_x, self.last_y = None, None
61
+
62
+ # visible canvas
63
+ self.canvas = tk.Canvas(root, width=400, height=400, bg="black")
64
+ self.canvas.pack(pady=10)
65
+
66
+ # pillow image for processing
67
+ self.pil_image = Image.new("L", (400, 400), "black")
68
+ self.pil_draw = ImageDraw.Draw(self.pil_image)
69
+
70
+ self.canvas.bind("<Button-1>", self.start_drawing)
71
+ self.canvas.bind("<B1-Motion>", self.draw)
72
+ self.canvas.bind("<ButtonRelease-1>", self.stop_drawing)
73
+
74
+ btn_frame = tk.Frame(root)
75
+ btn_frame.pack(pady=10)
76
+
77
+ self.predict_btn = tk.Button(btn_frame, text="Predict", command=self.predict, font=("Arial", 14), bg="#4CAF50", fg="white")
78
+ self.predict_btn.pack(side=tk.LEFT, padx=10)
79
+
80
+ self.clear_btn = tk.Button(btn_frame, text="Clear", command=self.clear_canvas, font=("Arial", 14), bg="#f44336", fg="white")
81
+ self.clear_btn.pack(side=tk.LEFT, padx=10)
82
+
83
+ self.result_label = tk.Label(root, text="Draw a digit and click Predict", font=("Arial", 18, "bold"))
84
+ self.result_label.pack(pady=15)
85
+
86
+ def start_drawing(self, event):
87
+ self.last_x, self.last_y = event.x, event.y
88
+ self.draw(event)
89
+
90
+ def draw(self, event):
91
+ brush_size = 30
92
+ if self.last_x is not None and self.last_y is not None:
93
+ # draw on tkinter
94
+ self.canvas.create_line(
95
+ self.last_x, self.last_y, event.x, event.y,
96
+ width=brush_size, fill="white", capstyle=tk.ROUND, smooth=True
97
+ )
98
+ # copy to pillow image
99
+ self.pil_draw.line(
100
+ [self.last_x, self.last_y, event.x, event.y],
101
+ fill=255, width=brush_size
102
+ )
103
+ self.last_x, self.last_y = event.x, event.y
104
+
105
+ def stop_drawing(self, event):
106
+ self.last_x, self.last_y = None, None
107
+
108
+ def clear_canvas(self):
109
+ self.canvas.delete("all")
110
+ self.result_label.config(text="Draw a digit and click Predict!")
111
+ # reset pillow image
112
+ self.pil_image = Image.new("L", (400, 400), "black")
113
+ self.pil_draw = ImageDraw.Draw(self.pil_image)
114
+
115
+ def predict(self):
116
+ # mnist size
117
+ img_28x28 = self.pil_image.resize((28, 28), Image.Resampling.LANCZOS)
118
+
119
+ transformer = transforms.Compose([
120
+ transforms.ToTensor(),
121
+ transforms.Normalize((0.1307,), (0.3081,))
122
+ ])
123
+ tensor_img = transformer(img_28x28).unsqueeze(0) # Add batch dimension [1, 1, 28, 28]
124
+
125
+ # model inference
126
+ with torch.no_grad():
127
+ output = model(tensor_img)
128
+ probabilities = torch.softmax(output, dim=1)
129
+ prediction = probabilities.argmax(dim=1).item()
130
+ confidence = probabilities[0][prediction].item() * 100
131
+ self.result_label.config(text=f"Prediction: {prediction} ({confidence:.2f}%)")
132
+
133
+ if __name__ == "__main__":
134
+ root = tk.Tk()
135
+ app = MNISTDrawer(root)
136
+ root.mainloop()