|
|
|
from flask import Flask, request, jsonify
|
|
from PIL import Image
|
|
import numpy as np
|
|
import io
|
|
import os
|
|
import tensorflow as tf
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
MODEL_PATH = "best_model.keras"
|
|
|
|
|
|
model = None
|
|
|
|
|
|
try:
|
|
if os.path.exists(MODEL_PATH):
|
|
model = tf.keras.models.load_model(MODEL_PATH, compile=False)
|
|
|
|
|
|
print(f"Model '{MODEL_PATH}' berhasil dimuat!")
|
|
else:
|
|
print(f"ERROR: File model tidak ditemukan di '{MODEL_PATH}'. API tidak akan berfungsi.")
|
|
|
|
model = None
|
|
except Exception as e:
|
|
print(f"ERROR: Gagal memuat model dari '{MODEL_PATH}': {e}. API tidak akan berfungsi.")
|
|
model = None
|
|
|
|
|
|
CLASS_LABELS = ['battery', 'biological', 'cardboard', 'clothes', 'glass', 'metal', 'paper', 'plastic', 'shoes', 'trash']
|
|
IMG_SIZE = (150, 150)
|
|
|
|
|
|
def preprocess_image(image_bytes):
|
|
"""
|
|
Melakukan preprocessing gambar:
|
|
1. Membuka gambar dari bytes.
|
|
2. Mengonversi ke mode RGB.
|
|
3. Mengubah ukuran (resize) gambar ke IMG_SIZE yang ditentukan.
|
|
4. Mengonversi gambar menjadi array NumPy.
|
|
5. Normalisasi nilai piksel (0-255 menjadi 0.0-1.0).
|
|
6. Menambahkan dimensi batch (untuk format input model Keras/TensorFlow).
|
|
"""
|
|
img = Image.open(io.BytesIO(image_bytes)).convert('RGB')
|
|
img = img.resize(IMG_SIZE)
|
|
img_array = np.array(img) / 255.0
|
|
return np.expand_dims(img_array, axis=0)
|
|
|
|
|
|
@app.route("/", methods=["GET"])
|
|
def home():
|
|
"""Endpoint utama untuk memverifikasi bahwa API berjalan."""
|
|
return "TrashGu ML API is running!"
|
|
|
|
|
|
@app.route("/classify", methods=["POST"])
|
|
def classify_image():
|
|
"""
|
|
Endpoint ini menerima gambar via POST request,
|
|
melakukan prediksi menggunakan model ML, dan mengembalikan hasilnya.
|
|
"""
|
|
if model is None:
|
|
|
|
return jsonify({"error": "Model ML belum dimuat. Mohon cek log Space Hugging Face."}), 500
|
|
|
|
|
|
if 'image' not in request.files:
|
|
return jsonify({"error": "Tidak ada file gambar yang disediakan."}), 400
|
|
|
|
image_file = request.files['image']
|
|
image_bytes = image_file.read()
|
|
|
|
|
|
try:
|
|
processed_image = preprocess_image(image_bytes)
|
|
except Exception as e:
|
|
app.logger.error(f"Gagal melakukan preprocessing gambar: {e}")
|
|
return jsonify({"error": f"Gagal melakukan preprocessing gambar: {e}"}), 400
|
|
|
|
|
|
|
|
|
|
|
|
is_anomaly = False
|
|
anomaly_score = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if is_anomaly:
|
|
return jsonify({
|
|
"prediction": "TIDAK_DIKETAHUI",
|
|
"confidence": 0.0,
|
|
"is_anomaly": True,
|
|
"anomaly_score": anomaly_score
|
|
}), 200
|
|
|
|
|
|
try:
|
|
predictions = model.predict(processed_image)
|
|
|
|
pred_array = predictions[0] if isinstance(predictions, np.ndarray) and predictions.ndim > 1 else predictions
|
|
|
|
|
|
predicted_class_index = np.argmax(pred_array)
|
|
|
|
predicted_label = CLASS_LABELS[predicted_class_index]
|
|
|
|
confidence = float(pred_array[predicted_class_index])
|
|
|
|
|
|
return jsonify({
|
|
"prediction": predicted_label,
|
|
"confidence": confidence,
|
|
"is_anomaly": False,
|
|
"anomaly_score": 0.0
|
|
}), 200
|
|
|
|
except Exception as e:
|
|
app.logger.error(f"Gagal melakukan inferensi model: {e}")
|
|
return jsonify({"error": f"Gagal melakukan inferensi model: {e}"}), 500
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
|
|
app.run(debug=True, host='0.0.0.0', port=int(os.environ.get("PORT", 5000)))
|
|
|