mnist-digit-api / model_service.py
endsieg97's picture
Upgrade MNIST model to CNN
a99ac4c verified
Raw
History Blame Contribute Delete
2.71 kB
from pathlib import Path
import numpy as np
import torch
from PIL import Image, ImageOps
from torch import nn
class DigitCNN(nn.Module):
def __init__(self):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(2),
nn.Flatten(),
nn.Linear(64 * 7 * 7, 128),
nn.ReLU(),
nn.Dropout(0.25),
nn.Linear(128, 10),
)
def forward(self, x):
return self.net(x)
ARTIFACT = torch.load(
Path(__file__).with_name("mnist_cnn.pt"),
map_location="cpu",
weights_only=False,
)
MODEL = DigitCNN()
MODEL.load_state_dict(ARTIFACT["model_state_dict"])
MODEL.eval()
ACCURACY = ARTIFACT["accuracy"]
def _to_image(image):
if image is None:
raise ValueError("Please draw or upload a digit image.")
if isinstance(image, dict):
image = image.get("composite") or image.get("image") or image.get("background")
if isinstance(image, (str, Path)):
return Image.open(image)
return Image.fromarray(np.asarray(image))
def preprocess_image(image):
img = _to_image(image).convert("L")
arr = np.asarray(img).astype("float32")
# Convert common black-on-white handwriting into MNIST-style white-on-black.
if arr.mean() > 127:
arr = 255.0 - arr
arr[arr < 25] = 0.0
rows, cols = np.where(arr > 0)
if len(rows) == 0 or len(cols) == 0:
raise ValueError("No digit-like strokes were detected.")
cropped = arr[rows.min() : rows.max() + 1, cols.min() : cols.max() + 1]
cropped_img = Image.fromarray(cropped.astype("uint8"), mode="L")
cropped_img.thumbnail((20, 20), Image.Resampling.LANCZOS)
canvas = Image.new("L", (28, 28), 0)
left = (28 - cropped_img.width) // 2
top = (28 - cropped_img.height) // 2
canvas.paste(cropped_img, (left, top))
preview = np.asarray(canvas).astype("float32") / 255.0
features = torch.from_numpy(preview.reshape(1, 1, 28, 28)).float()
return features, preview
def predict_digit(image):
features, preview = preprocess_image(image)
with torch.no_grad():
probabilities = torch.softmax(MODEL(features), dim=1).numpy()[0]
digit = int(np.argmax(probabilities))
confidence = float(probabilities[digit])
ranking = {
str(index): float(score)
for index, score in sorted(
enumerate(probabilities),
key=lambda item: item[1],
reverse=True,
)
}
return str(digit), confidence, ranking, preview