from flask import Flask, request, jsonify import numpy as np import cv2 # import your existing code from main import FingerQualityAssessor # or wherever the class lives from quality_analyzer import QualityConfig app = Flask(__name__) # Create once (so model/config not recreated per request) CONFIG = QualityConfig( target_width=640, blur_min=60.0, illum_min=50.0, illum_max=200.0, coverage_min=0.10, orientation_max_deviation=45.0, vertical_expected=True, overall_quality_threshold=0.70, ) ASSESSOR = FingerQualityAssessor(CONFIG) def _decode_uploaded_image(file_storage): # file_storage is a Werkzeug FileStorage from request.files data = file_storage.read() if not data: raise ValueError("Empty file") nparr = np.frombuffer(data, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # BGR image if img is None: raise ValueError("Could not decode image (invalid/unsupported format)") return img @app.get("/") def root(): return jsonify({ "status": "ok", "message": "Finger Quality API is running", "endpoints": ["/api/v1/finger-quality"] }), 200 @app.get("/health") def health(): return "OK", 200 @app.post("/api/v1/finger-quality") def finger_quality(): """ Expects: multipart/form-data with file field name 'image' Returns: JSON with result + feedback """ if "image" not in request.files: return jsonify({"error": "Missing file field 'image'"}), 400 # jsonify returns application/json response [web:7] try: bgr = _decode_uploaded_image(request.files["image"]) result, feedback, _debug = ASSESSOR.assess(bgr, draw_debug=False) # Build JSON-safe dict (do not return numpy types/tuples blindly) payload = { "result": { "blur_score": float(result.blur_score), "illumination_score": float(result.illumination_score), "coverage_ratio": float(result.coverage_ratio), "orientation_angle_deg": float(result.orientation_angle_deg), "blur_pass": bool(result.blur_pass), "illumination_pass": bool(result.illumination_pass), "coverage_pass": bool(result.coverage_pass), "orientation_pass": bool(result.orientation_pass), "quality_score": float(result.quality_score), "overall_pass": bool(result.overall_pass), "bbox": list(result.bbox) if result.bbox is not None else None, "contour_area": float(result.contour_area), }, "feedback": { "is_acceptable": bool(feedback.is_acceptable), "messages": [ { "severity": m.severity, "category": m.category, "message": m.message, } for m in (feedback.messages or []) ], }, } return jsonify(payload), 200 # jsonify formats JSON response conveniently [web:7] except ValueError as e: return jsonify({"error": str(e)}), 400 # jsonify is fine for structured errors [web:7] except Exception as e: # avoid leaking internals in production; log e server-side return jsonify({"error": "Internal server error"}), 500 # jsonify response [web:7] if __name__ == "__main__": app.run(host="0.0.0.0", port=7860)