Spaces:
Sleeping
Sleeping
_umannn commited on
Commit ·
244c30f
1
Parent(s): 4342c4c
Deploy Flask Batik Classifier
Browse files- Dockerfile +20 -0
- app.py +28 -0
- mobilenetv3_batik.tflite +3 -0
- requirements.txt +5 -0
- utils.py +47 -0
Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Gunakan Python versi stabil
|
| 2 |
+
FROM python:3.10
|
| 3 |
+
|
| 4 |
+
# Set folder kerja di dalam container
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
|
| 7 |
+
# Copy requirements.txt ke container
|
| 8 |
+
COPY requirements.txt .
|
| 9 |
+
|
| 10 |
+
# Install semua dependencies
|
| 11 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 12 |
+
|
| 13 |
+
# Copy semua file project (app.py, utils.py, model, dll)
|
| 14 |
+
COPY . .
|
| 15 |
+
|
| 16 |
+
# Expose port HuggingFace
|
| 17 |
+
EXPOSE 7860
|
| 18 |
+
|
| 19 |
+
# Jalankan Flask API
|
| 20 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify
|
| 2 |
+
from flask_cors import CORS
|
| 3 |
+
import os
|
| 4 |
+
from werkzeug.utils import secure_filename
|
| 5 |
+
from utils import predict_image
|
| 6 |
+
|
| 7 |
+
app = Flask(__name__)
|
| 8 |
+
CORS(app) # Allow cross-origin (Next.js ke Flask)
|
| 9 |
+
|
| 10 |
+
UPLOAD_FOLDER = 'uploads'
|
| 11 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 12 |
+
|
| 13 |
+
@app.route('/predict', methods=['POST'])
|
| 14 |
+
def predict():
|
| 15 |
+
if 'file' not in request.files:
|
| 16 |
+
return jsonify({'error': 'No file uploaded'}), 400
|
| 17 |
+
|
| 18 |
+
file = request.files['file']
|
| 19 |
+
filename = secure_filename(file.filename)
|
| 20 |
+
filepath = os.path.join(UPLOAD_FOLDER, filename)
|
| 21 |
+
file.save(filepath)
|
| 22 |
+
|
| 23 |
+
result = predict_image(filepath)
|
| 24 |
+
|
| 25 |
+
return jsonify(result)
|
| 26 |
+
|
| 27 |
+
if __name__ == '__main__':
|
| 28 |
+
app.run(debug=True)
|
mobilenetv3_batik.tflite
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:4ae89f2869d74e5335d53c7329d42ba51164cd733e5e7977d0648844e500e95f
|
| 3 |
+
size 4032392
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
flask
|
| 2 |
+
flask-cors
|
| 3 |
+
tensorflow==2.13.0
|
| 4 |
+
pillow
|
| 5 |
+
gunicorn
|
utils.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import tensorflow as tf
|
| 2 |
+
from tensorflow.keras.utils import img_to_array, load_img
|
| 3 |
+
import numpy as np
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
# Path model TFLite
|
| 7 |
+
model_path = r"D:\KULIAH UNS\KLASIFIKASI BATIK\training\mobilenetv3_batik.tflite"
|
| 8 |
+
|
| 9 |
+
# Load interpreter TFLite
|
| 10 |
+
interpreter = tf.lite.Interpreter(model_path=model_path)
|
| 11 |
+
interpreter.allocate_tensors()
|
| 12 |
+
|
| 13 |
+
# Ambil detail input & output
|
| 14 |
+
input_details = interpreter.get_input_details()
|
| 15 |
+
output_details = interpreter.get_output_details()
|
| 16 |
+
|
| 17 |
+
# Label kelas
|
| 18 |
+
class_names = [
|
| 19 |
+
'batik-bali', 'batik-betawi', 'batik-celup', 'batik-cendrawasih',
|
| 20 |
+
'batik-ceplok', 'batik-ciamis', 'batik-garutan', 'batik-gentongan',
|
| 21 |
+
'batik-kawung', 'batik-keraton', 'batik-lasem', 'batik-megamendung',
|
| 22 |
+
'batik-parang', 'batik-pekalongan', 'batik-priangan', 'batik-sekar',
|
| 23 |
+
'batik-sidoluhur', 'batik-sidomukti', 'batik-sogan', 'batik-tambal'
|
| 24 |
+
]
|
| 25 |
+
|
| 26 |
+
def predict_image(image_path):
|
| 27 |
+
# Load & preprocess image
|
| 28 |
+
image = load_img(image_path, target_size=(224, 224))
|
| 29 |
+
image = img_to_array(image) / 255.0
|
| 30 |
+
image = np.expand_dims(image, axis=0).astype(np.float32)
|
| 31 |
+
|
| 32 |
+
# Set tensor input
|
| 33 |
+
interpreter.set_tensor(input_details[0]['index'], image)
|
| 34 |
+
|
| 35 |
+
# Jalankan inference
|
| 36 |
+
interpreter.invoke()
|
| 37 |
+
|
| 38 |
+
# Ambil tensor output
|
| 39 |
+
predictions = interpreter.get_tensor(output_details[0]['index'])[0]
|
| 40 |
+
max_index = np.argmax(predictions)
|
| 41 |
+
predicted_class = class_names[max_index]
|
| 42 |
+
confidence = float(predictions[max_index]) * 100
|
| 43 |
+
|
| 44 |
+
return {
|
| 45 |
+
"class": predicted_class,
|
| 46 |
+
"confidence": round(confidence, 2)
|
| 47 |
+
}
|