Spaces:
Build error
Build error
first commit
Browse files- app +11 -0
- deploy/app.py +42 -0
- deploy/functions.py +27 -0
- deploy/model_mnist.h5 +3 -0
- deploy/requirements.txt +7 -0
- deploy/static/script.js +77 -0
- deploy/static/styles.css +86 -0
- deploy/templates/webpage.html +39 -0
app
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10
|
| 2 |
+
|
| 3 |
+
RUN apt-get update && apt-get install -y libgl1-mesa-glx
|
| 4 |
+
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
COPY ./deploy /app
|
| 8 |
+
|
| 9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 10 |
+
|
| 11 |
+
CMD ["python", "app.py", "--host", "0.0.0.0", "--port", "7860"]
|
deploy/app.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Import Library
|
| 2 |
+
from flask import Flask, request, jsonify, render_template, send_from_directory
|
| 3 |
+
import os
|
| 4 |
+
from tensorflow import keras
|
| 5 |
+
from keras.models import load_model
|
| 6 |
+
|
| 7 |
+
#Import functions and model
|
| 8 |
+
from functions import predict
|
| 9 |
+
|
| 10 |
+
model = load_model('model_mnist.h5')
|
| 11 |
+
|
| 12 |
+
app = Flask('meu app', template_folder='templates')
|
| 13 |
+
|
| 14 |
+
@app.route('/')
|
| 15 |
+
def index():
|
| 16 |
+
return render_template('webpage.html')
|
| 17 |
+
|
| 18 |
+
@app.route('/static/<path:filename>')
|
| 19 |
+
def serve_static(filename):
|
| 20 |
+
return send_from_directory(os.path.join(app.root_path, 'static'), filename)
|
| 21 |
+
|
| 22 |
+
@app.route('/process_drawing', methods=['POST'])
|
| 23 |
+
def process_drawing():
|
| 24 |
+
|
| 25 |
+
data = request.json
|
| 26 |
+
image_data = data.get("imageData")
|
| 27 |
+
|
| 28 |
+
if image_data:
|
| 29 |
+
|
| 30 |
+
value_1, value_2 = predict(image_data, model)
|
| 31 |
+
|
| 32 |
+
response = {
|
| 33 |
+
"value_1": value_1,
|
| 34 |
+
"value_2": value_2
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
return jsonify(response)
|
| 38 |
+
|
| 39 |
+
return jsonify({"response": "Erro: Dados de imagem não recebidos."})
|
| 40 |
+
|
| 41 |
+
if __name__ == '__main__':
|
| 42 |
+
app.run()
|
deploy/functions.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import base64
|
| 4 |
+
from PIL import Image
|
| 5 |
+
import io
|
| 6 |
+
|
| 7 |
+
def predict(image_input, model):
|
| 8 |
+
|
| 9 |
+
image_data_bytes = base64.b64decode(image_input)
|
| 10 |
+
|
| 11 |
+
image_stream = io.BytesIO(image_data_bytes)
|
| 12 |
+
|
| 13 |
+
image_pil = Image.open(image_stream)
|
| 14 |
+
|
| 15 |
+
image_np = np.array(image_pil)
|
| 16 |
+
|
| 17 |
+
image = cv2.resize(image_np, (28,28))[:, :, 3]
|
| 18 |
+
|
| 19 |
+
resized_image = image / 255.0
|
| 20 |
+
|
| 21 |
+
input_image = np.expand_dims(resized_image, axis=0)
|
| 22 |
+
|
| 23 |
+
predicted_probabilities = model.predict(input_image)
|
| 24 |
+
|
| 25 |
+
predicted_labels = np.argmax(predicted_probabilities, axis=1)
|
| 26 |
+
|
| 27 |
+
return predicted_labels[0].astype(str), np.around(predicted_probabilities.max()*100,3).astype(str)
|
deploy/model_mnist.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9235d310f346384d3f068edd3c096612d4a846bb8a7d2173cc50e650279f5d9c
|
| 3 |
+
size 5493688
|
deploy/requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#Let's install all the necessary libraries, specifying their versions
|
| 2 |
+
Flask==2.2.2
|
| 3 |
+
Werkzeug==2.2.2
|
| 4 |
+
tensorflow==2.12.0
|
| 5 |
+
opencv-python==4.8.1.78
|
| 6 |
+
numpy==1.23.1
|
| 7 |
+
Pillow==9.3.0
|
deploy/static/script.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
var canvas = document.getElementById('drawingCanvas');
|
| 2 |
+
var context = canvas.getContext('2d');
|
| 3 |
+
var isDrawing = false;
|
| 4 |
+
|
| 5 |
+
// Define a grossura da linha
|
| 6 |
+
context.lineWidth = 14.5; // Altere para o valor desejado em pixels
|
| 7 |
+
|
| 8 |
+
canvas.addEventListener('mousedown', function(e) {
|
| 9 |
+
isDrawing = true;
|
| 10 |
+
context.beginPath();
|
| 11 |
+
context.moveTo(e.clientX - canvas.getBoundingClientRect().left, e.clientY - canvas.getBoundingClientRect().top);
|
| 12 |
+
});
|
| 13 |
+
|
| 14 |
+
canvas.addEventListener('mousemove', function(e) {
|
| 15 |
+
if (!isDrawing) return;
|
| 16 |
+
context.lineTo(e.clientX - canvas.getBoundingClientRect().left, e.clientY - canvas.getBoundingClientRect().top);
|
| 17 |
+
context.stroke();
|
| 18 |
+
});
|
| 19 |
+
|
| 20 |
+
canvas.addEventListener('mouseup', function() {
|
| 21 |
+
isDrawing = false;
|
| 22 |
+
});
|
| 23 |
+
|
| 24 |
+
document.getElementById("clearCanvas").addEventListener("click", function() {
|
| 25 |
+
var canvas = document.getElementById("drawingCanvas");
|
| 26 |
+
var context = canvas.getContext("2d");
|
| 27 |
+
|
| 28 |
+
// Limpa o canvas preenchendo-o com uma cor de fundo (pode ser branco)
|
| 29 |
+
context.fillStyle = "white"; // Altere a cor de fundo desejada
|
| 30 |
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
| 31 |
+
|
| 32 |
+
// Você pode adicionar outras ações de limpeza, se necessário
|
| 33 |
+
});
|
| 34 |
+
|
| 35 |
+
document.getElementById("sendDrawing").addEventListener("click", function() {
|
| 36 |
+
|
| 37 |
+
// Obtenha a imagem em base64
|
| 38 |
+
var imageDataURL = canvas.toDataURL("image/png");
|
| 39 |
+
|
| 40 |
+
// Verifique se a imagem base64 contém dados
|
| 41 |
+
if (imageDataURL.indexOf("base64,") === -1) {
|
| 42 |
+
alert("Desenhe algo no canvas antes de enviar.");
|
| 43 |
+
return;
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
// Remova o prefixo "data:image/png;base64,"
|
| 47 |
+
var base64Data = imageDataURL.replace(/^data:image\/(png|jpeg);base64,/, "");
|
| 48 |
+
|
| 49 |
+
// Adicione padding de acordo com o comprimento da string
|
| 50 |
+
while (base64Data.length % 4 !== 0) {
|
| 51 |
+
base64Data += "=";
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
var xhr = new XMLHttpRequest();
|
| 55 |
+
|
| 56 |
+
xhr.open("POST", "/process_drawing", true);
|
| 57 |
+
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
|
| 58 |
+
var data = JSON.stringify({ "imageData": base64Data });
|
| 59 |
+
xhr.send(data);
|
| 60 |
+
|
| 61 |
+
xhr.onload = function() {
|
| 62 |
+
if (xhr.status === 200) {
|
| 63 |
+
|
| 64 |
+
var response = JSON.parse(xhr.responseText);
|
| 65 |
+
|
| 66 |
+
var value_1 = response.value_1;
|
| 67 |
+
var value_2 = response.value_2;
|
| 68 |
+
|
| 69 |
+
document.getElementById("digitoValor").textContent = value_1;
|
| 70 |
+
document.getElementById("probabilidadeValor").textContent = value_2;
|
| 71 |
+
|
| 72 |
+
} else {
|
| 73 |
+
|
| 74 |
+
document.getElementById("responseText").textContent = "Erro na solicitação.";
|
| 75 |
+
}
|
| 76 |
+
};
|
| 77 |
+
});
|
deploy/static/styles.css
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body {
|
| 2 |
+
margin: 0;
|
| 3 |
+
padding: 0;
|
| 4 |
+
display: flex;
|
| 5 |
+
flex-direction: column;
|
| 6 |
+
font-family: Arial, sans-serif;
|
| 7 |
+
background: linear-gradient(to bottom, #FF66A3, #007BFF);
|
| 8 |
+
background-repeat: no-repeat;
|
| 9 |
+
background-attachment: fixed;
|
| 10 |
+
background-size: cover;
|
| 11 |
+
background-position: center;
|
| 12 |
+
color: #333;
|
| 13 |
+
height: 100vh;
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
header {
|
| 17 |
+
/* background-color: #1f1f1f; */
|
| 18 |
+
color: #fff;
|
| 19 |
+
text-align: center;
|
| 20 |
+
padding: 0px 0;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
#button-container {
|
| 24 |
+
text-align: center;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
#button-container, #drawingCanvas{
|
| 28 |
+
flex-direction: column;
|
| 29 |
+
margin: 5px;
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
#sendDrawing, #clearCanvas {
|
| 33 |
+
display: inline-block;
|
| 34 |
+
margin: 10px; /* Espaçamento entre os botões */
|
| 35 |
+
padding: 10px 20px; /* Espaçamento interno dos botões */
|
| 36 |
+
background-color: #1265e0; /* Cor de fundo do botão */
|
| 37 |
+
color: #fff; /* Cor do texto do botão */
|
| 38 |
+
border: none;
|
| 39 |
+
border-radius: 5px;
|
| 40 |
+
cursor: pointer;
|
| 41 |
+
transition: background-color 0.3s; /* Efeito de transição de cor ao passar o mouse */
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
#sendDrawing:hover, #clearCanvas:hover {
|
| 45 |
+
background-color: #023d7c; /* Cor do botão ao passar o mouse */
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
#textbox-container {
|
| 49 |
+
text-align: center;
|
| 50 |
+
margin-top: 20px; /* Espaçamento acima dos elementos de texto */
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
#bigChar {
|
| 54 |
+
font-size: 40px; /* Tamanho grande para o caracter */
|
| 55 |
+
margin-bottom: 10px; /* Espaçamento abaixo do caracter */
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
#number {
|
| 59 |
+
font-size: 24px; /* Tamanho do número */
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
.grid-container {
|
| 63 |
+
display: flex;
|
| 64 |
+
flex-direction: column;
|
| 65 |
+
align-items: center;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
.container {
|
| 69 |
+
display: flex;
|
| 70 |
+
justify-content: center;
|
| 71 |
+
margin-top: 100px;
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
#grid-container, #textbox-container{
|
| 75 |
+
margin-left: 150px;
|
| 76 |
+
margin-right: 150px;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
.numero-container {
|
| 80 |
+
width: 280px;
|
| 81 |
+
height: 280px;
|
| 82 |
+
background-color: #fff;
|
| 83 |
+
color: #000;
|
| 84 |
+
text-align: center;
|
| 85 |
+
line-height: 100px;
|
| 86 |
+
}
|
deploy/templates/webpage.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='styles.css') }}">
|
| 7 |
+
<title>Digit Recognizer</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<header>
|
| 11 |
+
<h1>Digit Recognizer</h1>
|
| 12 |
+
</header>
|
| 13 |
+
|
| 14 |
+
<div class="container">
|
| 15 |
+
|
| 16 |
+
<div class="grid-container">
|
| 17 |
+
|
| 18 |
+
<canvas id="drawingCanvas" width="280" height="280" style="border: 1px solid black; background-color: white;"></canvas>
|
| 19 |
+
|
| 20 |
+
<div id="button-container">
|
| 21 |
+
|
| 22 |
+
<button id="sendDrawing">Enviar Desenho</button>
|
| 23 |
+
<button id="clearCanvas">Limpar Desenho</button>
|
| 24 |
+
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
| 25 |
+
|
| 26 |
+
</div>
|
| 27 |
+
|
| 28 |
+
</div>
|
| 29 |
+
|
| 30 |
+
<div id="textbox-container">
|
| 31 |
+
|
| 32 |
+
<div id="bigChar">Seu dígito é: <span id="digitoValor"></span></div>
|
| 33 |
+
<div id="number">Propabilidade da previsão: <span id="probabilidadeValor"></span>%</div>
|
| 34 |
+
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
</div>
|
| 38 |
+
</body>
|
| 39 |
+
</html>
|