Spaces:
Sleeping
Sleeping
Add JSON API endpoint for mobile app with CORS support
Browse files- requirements.txt +3 -3
- src/app.py +38 -1
requirements.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
python-multipart==0.0.9
|
| 4 |
tensorflow-cpu>=2.16.1
|
| 5 |
pillow==10.2.0
|
| 6 |
numpy==1.26.4
|
| 7 |
opencv-python-headless==4.9.0.80
|
|
|
|
|
|
| 1 |
+
flask>=2.3.0
|
| 2 |
+
flask-cors>=4.0.0
|
|
|
|
| 3 |
tensorflow-cpu>=2.16.1
|
| 4 |
pillow==10.2.0
|
| 5 |
numpy==1.26.4
|
| 6 |
opencv-python-headless==4.9.0.80
|
| 7 |
+
gunicorn>=21.0.0
|
src/app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
-
from flask import Flask, render_template, request, flash, redirect, url_for
|
|
|
|
| 2 |
from tensorflow.keras.models import load_model
|
| 3 |
import numpy as np
|
| 4 |
from PIL import Image
|
|
@@ -10,6 +11,7 @@ import json
|
|
| 10 |
import time
|
| 11 |
|
| 12 |
app = Flask(__name__)
|
|
|
|
| 13 |
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' # Secret key for flash messages
|
| 14 |
|
| 15 |
# Define allowed extensions for image uploads
|
|
@@ -102,6 +104,41 @@ def predict():
|
|
| 102 |
flash('Invalid file type. Please upload an image (png, jpg, jpeg).')
|
| 103 |
return redirect(url_for('index'))
|
| 104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
def cleanup_old_images(folder='static', age_seconds=3600):
|
| 106 |
"""
|
| 107 |
Removes files in the specified folder that are older than age_seconds.
|
|
|
|
| 1 |
+
from flask import Flask, render_template, request, flash, redirect, url_for, jsonify
|
| 2 |
+
from flask_cors import CORS
|
| 3 |
from tensorflow.keras.models import load_model
|
| 4 |
import numpy as np
|
| 5 |
from PIL import Image
|
|
|
|
| 11 |
import time
|
| 12 |
|
| 13 |
app = Flask(__name__)
|
| 14 |
+
CORS(app) # Enable CORS for mobile app
|
| 15 |
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/' # Secret key for flash messages
|
| 16 |
|
| 17 |
# Define allowed extensions for image uploads
|
|
|
|
| 104 |
flash('Invalid file type. Please upload an image (png, jpg, jpeg).')
|
| 105 |
return redirect(url_for('index'))
|
| 106 |
|
| 107 |
+
@app.route('/api/predict', methods=['POST'])
|
| 108 |
+
def api_predict():
|
| 109 |
+
"""
|
| 110 |
+
JSON API endpoint for mobile app predictions.
|
| 111 |
+
Returns: JSON with label and confidence
|
| 112 |
+
"""
|
| 113 |
+
if 'file' not in request.files:
|
| 114 |
+
return jsonify({'error': 'No file provided'}), 400
|
| 115 |
+
|
| 116 |
+
file = request.files['file']
|
| 117 |
+
|
| 118 |
+
if file.filename == '':
|
| 119 |
+
return jsonify({'error': 'No file selected'}), 400
|
| 120 |
+
|
| 121 |
+
if file and allowed_file(file.filename):
|
| 122 |
+
img = Image.open(io.BytesIO(file.read()))
|
| 123 |
+
img_np = np.array(img)
|
| 124 |
+
|
| 125 |
+
# Preprocess image for classification model
|
| 126 |
+
img_resized = cv2.resize(img_np, (300, 300))
|
| 127 |
+
img_reshaped = np.reshape(img_resized, (1, 300, 300, 3))
|
| 128 |
+
|
| 129 |
+
# Run prediction
|
| 130 |
+
prediction = classification_model.predict(img_reshaped)
|
| 131 |
+
label_index = np.argmax(prediction)
|
| 132 |
+
label = CLASSIFICATION_LABELS[label_index]
|
| 133 |
+
confidence = float(prediction[0][label_index])
|
| 134 |
+
|
| 135 |
+
return jsonify({
|
| 136 |
+
'label': label,
|
| 137 |
+
'confidence': confidence
|
| 138 |
+
})
|
| 139 |
+
else:
|
| 140 |
+
return jsonify({'error': 'Invalid file type'}), 400
|
| 141 |
+
|
| 142 |
def cleanup_old_images(folder='static', age_seconds=3600):
|
| 143 |
"""
|
| 144 |
Removes files in the specified folder that are older than age_seconds.
|