feat: add basic api with really bad model

Refs: OPS-85
This commit is contained in:
Christoph J. Scherr 2025-03-23 16:49:07 +01:00
parent 440bf6eacb
commit 47d4d9e4b9
No known key found for this signature in database
GPG key ID: 9EB784BB202BB7BB
4 changed files with 101 additions and 7 deletions

View file

@ -2,9 +2,12 @@
name = "senju" name = "senju"
version = "0.1.0" version = "0.1.0"
description = "API / Webservice for Phrases/Words/Kanji/Haiku" description = "API / Webservice for Phrases/Words/Kanji/Haiku"
authors = [{ name = "Christoph J. Scherr", email = "software@cscherr.de" },{name = "Moritz Marquard", email="mrmarquard@protonmail.com"}] authors = [
{ name = "Christoph J. Scherr", email = "software@cscherr.de" },
{ name = "Moritz Marquard", email = "mrmarquard@protonmail.com" },
]
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10,<3.13"
dependencies = [ dependencies = [
"jinja2 (>=3.1.5,<4.0.0)", "jinja2 (>=3.1.5,<4.0.0)",
"pytest>=7.0.0", "pytest>=7.0.0",
@ -13,6 +16,8 @@ dependencies = [
"requests (>=2.32.3,<3.0.0)", "requests (>=2.32.3,<3.0.0)",
"coverage (>=7.6.12,<8.0.0)", "coverage (>=7.6.12,<8.0.0)",
"pytest-httpserver (>=1.1.2,<2.0.0)", "pytest-httpserver (>=1.1.2,<2.0.0)",
"pillow (>=11.1.0,<12.0.0)",
"tensorflow (>=2.19.0,<3.0.0)",
] ]

40
senju/image_reco.py Normal file
View file

@ -0,0 +1,40 @@
import numpy as np
from PIL import Image
import keras
import io
g_model = None
class SimpleClassifier:
def __init__(self):
global g_model
if g_model is None:
g_model = keras.applications.MobileNetV2(weights="imagenet")
self.model = g_model
def classify(self, image_data):
# Convert uploaded bytes to image
img = Image.open(io.BytesIO(image_data)).convert("RGB")
img = img.resize((224, 224))
img_array = np.array(img) / 255.0
img_array = np.expand_dims(img_array, axis=0)
# Get predictions
predictions = self.model.predict(img_array)
results = keras.applications.mobilenet_v2.decode_predictions(
predictions, top=5)[0]
data: dict = {}
all_labels: list[dict] = []
data["best_guess"] = {"label": "", "confidence": float(0)}
for _, label, score in results:
score = float(score)
datapoint = {"label": label, "confidence": score}
all_labels.append(datapoint)
if data["best_guess"]["confidence"] < score:
data["best_guess"] = datapoint
data["all"] = all_labels
return data

View file

@ -6,6 +6,7 @@ from flask import (Flask, redirect, render_template, request, url_for,
send_from_directory) send_from_directory)
from senju.haiku import Haiku from senju.haiku import Haiku
from senju.image_reco import SimpleClassifier
from senju.store_manager import StoreManager from senju.store_manager import StoreManager
import os import os
@ -60,6 +61,23 @@ def scan_view():
) )
@app.route("/api/v1/image_reco", methods=['POST'])
def image_recognition():
# note that the classifier is a singleton
classifier = SimpleClassifier()
if 'image' not in request.files:
return "No image file provided", 400
image_file = request.files['image']
image_data = image_file.read()
try:
results = classifier.classify(image_data)
return results
except Exception as e:
return str(e), 500
@app.route("/api/v1/haiku", methods=['POST']) @app.route("/api/v1/haiku", methods=['POST'])
def generate_haiku(): def generate_haiku():
if request.method == 'POST': if request.method == 'POST':

View file

@ -56,15 +56,46 @@ function handleSubmit() {
// Hide error // Hide error
errorMessage.classList.add("hidden"); errorMessage.classList.add("hidden");
// Show response box // Show loading state
document.getElementById("ai-response").textContent = "Analyzing image...";
responseBox.classList.remove("opacity-0"); responseBox.classList.remove("opacity-0");
// Example response // Get the file from the input
document.getElementById("ai-response").textContent = const file = dropzoneFile.files[0];
"Dominic Monaghan interviewing Elijah Wood if he will wear wigs";
// Create FormData object to send the file
const formData = new FormData();
formData.append("image", file);
// Send the image to your backend API
fetch("/api/v1/image_reco", {
method: "POST",
body: formData,
})
.then((response) => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
return response.json();
})
.then((data) => {
// Extract top result and display it
if (data.results && data.results.length > 0) {
const topResult = data.results[0];
document.getElementById("ai-response").textContent =
`${topResult.label} (${Math.round(topResult.confidence * 100)}% confidence)`;
} else {
document.getElementById("ai-response").textContent =
"Could not identify the image";
}
})
.catch((error) => {
console.error("Error:", error);
document.getElementById("ai-response").textContent =
"Error analyzing image";
});
} else { } else {
errorMessage.classList.remove("hidden"); errorMessage.classList.remove("hidden");
uploadArea.classList.add("shake"); uploadArea.classList.add("shake");
setTimeout(() => { setTimeout(() => {
uploadArea.classList.remove("shake"); uploadArea.classList.remove("shake");