Initial commit

This commit is contained in:
2025-11-23 18:03:01 -08:00
commit 68b9adef1a
14 changed files with 423 additions and 0 deletions

29
app/app.py Normal file
View File

@@ -0,0 +1,29 @@
from base64 import b64decode
from flask import Flask, render_template, request
import io
from keras.preprocessing.image import img_to_array
import model
import numpy as np
from PIL import Image
app = Flask(__name__)
HOST="0.0.0.0"
PORT=3000
@app.route("/")
def index():
return render_template("index.html")
@app.route("/shape_model")
def shape_model():
encoded_img = request.args["img"]
encoded_img = encoded_img.replace("data:image/png;base64,", "", 1)
img = b64decode(encoded_img)
img = Image.open(io.BytesIO(img))
img = img.convert("L")
img = img_to_array(img)
prediction = model.run_model(img)
return prediction
if __name__ == "__main__":
app.run(HOST, port=PORT)

12
app/model.py Normal file
View File

@@ -0,0 +1,12 @@
from keras import models, layers
import numpy as np
model = models.load_model("model/shape_model.keras")
labels = ["circle ○", "rectangle ▭", "square □", "triangle △"]
def run_model(image):
img = np.expand_dims(image, axis=0)
prediction = np.argmax(model.predict(img))
return labels[prediction]
if __name__=="__main__":
print(run_model(input("Image path: ")))

BIN
app/model/shape_model.keras Normal file

Binary file not shown.

43
app/requirements.txt Normal file
View File

@@ -0,0 +1,43 @@
absl-py==2.3.1
astunparse==1.6.3
blinker==1.9.0
certifi==2025.10.5
charset-normalizer==3.4.4
click==8.3.0
Flask==3.1.2
flatbuffers==25.9.23
gast==0.6.0
google-pasta==0.2.0
grpcio==1.76.0
gunicorn==23.0.0
h5py==3.15.1
idna==3.11
itsdangerous==2.2.0
Jinja2==3.1.6
keras==3.11.3
libclang==18.1.1
Markdown==3.9
markdown-it-py==4.0.0
MarkupSafe==3.0.3
mdurl==0.1.2
ml_dtypes==0.5.3
namex==0.1.0
numpy==2.2.6
opencv-python==4.12.0.88
opt_einsum==3.4.0
optree==0.17.0
packaging==25.0
pillow==12.0.0
protobuf==6.33.0
Pygments==2.19.2
requests==2.32.5
rich==14.2.0
six==1.17.0
tensorboard==2.20.0
tensorboard-data-server==0.7.2
tensorflow==2.20.0
termcolor==3.1.0
typing_extensions==4.15.0
urllib3==2.5.0
Werkzeug==3.1.3
wrapt==2.0.0

BIN
app/static/quentin.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

57
app/static/script.js Normal file
View File

@@ -0,0 +1,57 @@
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
var aiBox = document.getElementById("shapeBox");
var isDragging = false;
function draw(e){
var canvas_width = 0.4 * document.documentElement.clientWidth;
if (document.documentElement.clientWidth <= 1000){
canvas_width = 0.8 * document.documentElement.clientWidth;
}
var rect = canvas.getBoundingClientRect();
if (e.type.includes(`touch`)) {
const { touches, changedTouches } = e.originalEvent ?? e;
const touch = touches[0] ?? changedTouches[0];
var posx = (touch.pageX - rect.left) * 64 / canvas_width;
var posy = (touch.pageY - rect.top) * 64 / canvas_width;
} else if (e.type.includes(`mouse`)) {
var posx = (e.clientX - rect.left) * 64 / canvas_width;
var posy = (e.clientY - rect.top) * 64 / canvas_width;
}
if (isDragging){
ctx.fillStyle = "#000000";
ctx.beginPath()
ctx.arc(posx, posy, 1, 0, 2*Math.PI);
ctx.fill();
}
}
function clear_canvas(){
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.fillRect(0, 0, 64, 64);
ctx.fill();
}
function send_image(){
var img = c.toDataURL();
const params = new URLSearchParams();
params.append("img", img);
fetch(`/shape_model?${params}`).then(
function (r) {return r.text();}
).then(
function (r) {aiBox.innerHTML = r;}
);
}
clear_canvas();
setInterval(send_image, 1000);
c.addEventListener("mousemove", draw);
c.addEventListener('touchmove', draw);
c.addEventListener('mousedown', function(e){isDragging = true;});
c.addEventListener('touchstart', function(e){isDragging = true;});
c.addEventListener('mouseup', function(e){isDragging = false;});
c.addEventListener('touchend', function(e){isDragging = false;});

60
app/static/style.css Normal file
View File

@@ -0,0 +1,60 @@
html, body{
margin: 0px;
font-family: "Lexend", sans-serif;
font-weight: 300;
overscroll-behavior-y: contain;
overflow-x: hidden;
}
#textArea{
background: #f8f8ff;
width: 40vw;
height: 100vh;
float: left;
display: flex;
justify-content: center;
align-items: center;
}
#drawingArea{
width: 60vw;
float: right;
}
#canvas{
border-style: solid;
border-width: 4px;
margin-left: 10vw;
margin-right: 10vw;
margin-top: calc(50vh - 20vw);
width:40vw;
height:40vw;
}
#quentinImg{
display: inline-block;
height: 1em;
width: auto;
border-radius: 30%;
}
h1{
font-size: 64px;
}
h2{
font-size: 48px;
}
p{
font-size: 24px;
}
@media only screen and (max-width:1000px) {
#textArea{
width: 100vw;
height: 20vh;
}
#drawingArea{
width: 100vw;
height: 80vh;
}
#canvas{
width: 80vw;
height: 80vw;
margin-top: calc(40vh - 40vw);
}
}

24
app/templates/index.html Normal file
View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>hello @quentinbkk i have found ur github :D</title>
<link href="/static/style.css" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@100..900&display=swap" rel="stylesheet">
</head>
<body>
<div id="textArea">
<div>
<h1>Shape AI</h1>
<h2>I see a <span id="shapeBox">...</span></h2>
<p>Based on <a href="https://github.com/quentinbkk/shapeAI">ShapeAI</a> by <a href="https://github.com/quentinbkk">@quentinbkk</a> <img id="quentinImg" src="/static/quentin.jpg"></p>
</div>
</div>
<div id="drawingArea">
<canvas id="canvas" id="canvas", width=64px, height=64px></canvas><br>
<center><h3 onclick="clear_canvas()">Reset</h3></center>
</div>
</body>
<script src="/static/script.js"></script>
</html>