TIF_E4253362/backend/app.py

175 lines
5.5 KiB
Python

"""
Flask API Server untuk Aplikasi Penerjemah Bahasa Isyarat Indonesia.
Menyediakan REST API endpoint untuk frontend React:
- POST /init : Inisialisasi session inferensi berdasarkan kategori (angka, abjad_bisindo, abjad_sibi, kata)
- POST /detect : Memproses frame kamera (base64) dari frontend dan mengembalikan hasil prediksi
"""
import sys
import uuid
import time
import base64
import numpy as np
import cv2
from pathlib import Path
from flask import Flask, request, jsonify
from flask_cors import CORS
# Menambahkan root folder backend ke sys.path
BASE_DIR = Path(__file__).parent
sys.path.insert(0, str(BASE_DIR))
from utils.inference import GestureRecognizer
from utils.config import MODEL_DIR, WINDOW_SIZE, CATEGORIES
app = Flask(__name__)
# Mengizinkan Cross-Origin Requests (CORS) agar Frontend React (port 3000) bisa terhubung
CORS(app, resources={r"/*": {"origins": "*"}})
# Dictionary penyimpanan session inferensi
# Format: { session_id: { "category": str, "recognizer": GestureRecognizer, "last_accessed": float } }
sessions = {}
# Timeout session (10 menit tanpa aktivitas -> cleanup)
SESSION_TIMEOUT_SECONDS = 600
def cleanup_expired_sessions():
"""Hapus session yang sudah tidak aktif untuk menghemat memory."""
now = time.time()
expired = [
sid for sid, sess in sessions.items()
if now - sess.get("last_accessed", 0) > SESSION_TIMEOUT_SECONDS
]
for sid in expired:
try:
sessions[sid]["recognizer"].close()
except Exception:
pass
del sessions[sid]
@app.route("/", methods=["GET"])
def index():
return jsonify({
"status": "online",
"message": "Backend Penerjemah Bahasa Isyarat Indonesia (TCN) Siap!",
"categories": CATEGORIES,
"endpoints": {
"/init": "POST - Inisialisasi session model",
"/detect": "POST - Kirim frame base64 dan dapatkan prediksi"
}
})
@app.route("/init", methods=["POST"])
def init_session():
"""Inisialisasi session inferensi untuk frontend."""
cleanup_expired_sessions()
data = request.get_json() or {}
category = data.get("category", "kata")
if category not in CATEGORIES:
return jsonify({
"error": f"Kategori '{category}' tidak valid. Pilihan kategori: {CATEGORIES}"
}), 400
model_path = MODEL_DIR / category / f"model_{category}.pt"
encoder_path = MODEL_DIR / category / "label_encoder.json"
if not model_path.exists():
return jsonify({
"error": f"Model untuk kategori '{category}' tidak ditemukan di {model_path}"
}), 404
if not encoder_path.exists():
return jsonify({
"error": f"Label encoder untuk kategori '{category}' tidak ditemukan di {encoder_path}"
}), 404
try:
recognizer = GestureRecognizer(
model_path=model_path,
label_encoder_path=encoder_path,
window_size=WINDOW_SIZE,
draw_landmarks=False
)
session_id = str(uuid.uuid4())
sessions[session_id] = {
"category": category,
"recognizer": recognizer,
"last_accessed": time.time()
}
print(f"[INIT] Session {session_id} dibuat untuk kategori: {category}")
return jsonify({
"session_id": session_id,
"category": category,
"status": "ready"
}), 200
except Exception as e:
print(f"[ERROR] Gagal inisialisasi session: {e}")
return jsonify({"error": f"Gagal inisialisasi model: {str(e)}"}), 500
@app.route("/detect", methods=["POST"])
def detect_frame():
"""Memproses 1 frame JPEG base64 dari frontend dan mengembalikan hasil prediksi."""
data = request.get_json() or {}
session_id = data.get("session_id")
frame_data = data.get("frame")
if not session_id or session_id not in sessions:
return jsonify({"error": "Session ID tidak ditemukan atau expired. Silakan muat ulang halaman."}), 400
if not frame_data:
return jsonify({"error": "Data frame gambar tidak boleh kosong."}), 400
session = sessions[session_id]
session["last_accessed"] = time.time()
recognizer = session["recognizer"]
try:
# Decode base64 image dari dataURL format (data:image/jpeg;base64,...)
if "," in frame_data:
frame_data = frame_data.split(",")[1]
image_bytes = base64.b64decode(frame_data)
nparr = np.frombuffer(image_bytes, np.uint8)
frame = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
if frame is None:
return jsonify({"error": "Gagal mendekode gambar dari frame data."}), 400
# Process frame
label, confidence = recognizer.process_frame(frame)
buffer_size = len(recognizer.buffer)
window_size = recognizer.window_size
# Print log untuk debug
if buffer_size > 0:
print(f"[{session['category'].upper()}] Buffer: {buffer_size}/{window_size} | Prediksi: {label if label else '...'}")
return jsonify({
"label": label if label is not None else None,
"confidence": float(confidence) if confidence is not None else 0.0,
"buffer_size": buffer_size,
"window_size": window_size
}), 200
except Exception as e:
print(f"[ERROR] Exception saat memproses frame: {e}")
return jsonify({"error": f"Error memproses frame: {str(e)}"}), 500
if __name__ == "__main__":
print("=" * 60)
print(" STARTING BACKEND FLASK SERVER (PORT 5000)")
print("=" * 60)
app.run(host="0.0.0.0", port=5000, debug=True)