diff --git a/app/main.py b/app/main.py index b978c2f2..0635c724 100644 --- a/app/main.py +++ b/app/main.py @@ -70,6 +70,10 @@ def calib_validation(): """ if request.method == "POST": return session_route.calib_results() - return Response( - "Invalid request method for route", status=405, mimetype="application/json" - ) + return Response('Invalid request method for route', status=405, mimetype='application/json') + +@app.route('/api/session/batch_predict', methods=['POST']) +def batch_predict(): + if request.method == 'POST': + return session_route.batch_predict() + return Response('Invalid request method for route', status=405, mimetype='application/json') diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 00000000..ba4582f3 --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,20 @@ +blinker==1.9.0 +click==8.1.8 +Flask==3.1.0 +flask-cors==5.0.1 +itsdangerous==2.2.0 +Jinja2==3.1.6 +joblib==1.4.2 +MarkupSafe==3.0.2 +numpy==2.2.4 +pandas==2.2.3 +python-dateutil==2.9.0.post0 +pytz==2025.2 +scikit-learn==1.6.1 +scipy==1.15.2 +six==1.17.0 +threadpoolctl==3.6.0 +tzdata==2025.2 +Werkzeug==3.1.3 +gunicorn==23.0.0 +requests==2.31.0 \ No newline at end of file diff --git a/app/routes/session.py b/app/routes/session.py index 0872b1b8..38eec9e9 100644 --- a/app/routes/session.py +++ b/app/routes/session.py @@ -6,7 +6,15 @@ import csv from pathlib import Path +import os +import pandas as pd +import traceback +import re +<<<<<<< HEAD +import requests +======= from flask import Flask, request, Response, send_file +>>>>>>> 42a70612727088340cf95589066fb593eb246472 # Local imports from app from app.services.storage import save_file_locally @@ -147,26 +155,13 @@ def calib_results(): - """ - Generate calibration results. - - This function generates calibration results based on the provided form data. - It saves the calibration points to a CSV file. Then, it uses the gaze_tracker module to predict the calibration results. - - Returns: - Response: A JSON response containing the calibration results. - - Raises: - IOError: If there is an error while writing to the CSV files. - """ - # Get form data from request - file_name = json.loads(request.form["file_name"]) - fixed_points = json.loads(request.form["fixed_circle_iris_points"]) - calib_points = json.loads(request.form["calib_circle_iris_points"]) - screen_height = json.loads(request.form["screen_height"]) - screen_width = json.loads(request.form["screen_width"]) - k = json.loads(request.form["k"]) - model = json.loads(request.form["model"]) + from_ruxailab = json.loads(request.form['from_ruxailab']) + file_name = json.loads(request.form['file_name']) + fixed_points = json.loads(request.form['fixed_circle_iris_points']) + calib_points = json.loads(request.form['calib_circle_iris_points']) + screen_height = json.loads(request.form['screen_height']) + screen_width = json.loads(request.form['screen_width']) + k = json.loads(request.form['k']) # Generate csv dataset of calibration points os.makedirs( @@ -219,15 +214,100 @@ def calib_results(): except IOError: print("I/O error") - # data = gaze_tracker.train_to_validate_calib(calib_csv_file, predict_csv_file) + # Run prediction + data = gaze_tracker.predict(calib_csv_file, calib_csv_file, k) + + if from_ruxailab: + try: + payload = { + "session_id": file_name, + "model": data, + "screen_height": screen_height, + "screen_width": screen_width, + "k": k + } + + RUXAILAB_WEBHOOK_URL = "https://receivecalibration-ffptzpxikq-uc.a.run.app" + + print("file_name:", file_name) - # Predict calibration results - data = gaze_tracker.predict(calib_csv_file, k, model_X=model, model_Y=model) + resp = requests.post(RUXAILAB_WEBHOOK_URL, json=payload) + print("Enviado para RuxaiLab:", resp.status_code, resp.text) + except Exception as e: + print("Erro ao enviar para RuxaiLab:", e) - # Return calibration results - return Response(json.dumps(data), status=200, mimetype="application/json") + return Response(json.dumps(data), status=200, mimetype='application/json') + +def batch_predict(): + try: + data = request.get_json() + + iris_data = data['iris_tracking_data'] + k = data.get('k', 3) + screen_height = data.get('screen_height') + screen_width = data.get('screen_width') + + base_path = Path().absolute() / 'app/services/calib_validation/csv/data' + calib_csv_path = base_path / 'vcczxvzxcv_fixed_train_data.csv' + predict_csv_path = base_path / 'temp_batch_predict.csv' + + print(f"Calib CSV Path: {calib_csv_path}") + print(f"Predict CSV Path: {predict_csv_path}") + print(f"Iris data sample (até 3): {iris_data[:3]}") + + # Debug: colunas do CSV de calibração + df_calib = pd.read_csv(calib_csv_path) + print("Colunas do CSV de calibração:", df_calib.columns.tolist()) + + # Cria CSV temporário com dados de íris para predição + with open(predict_csv_path, 'w', newline='') as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=[ + 'left_iris_x', 'left_iris_y', 'right_iris_x', 'right_iris_y' + ]) + writer.writeheader() + for item in iris_data: + writer.writerow({ + 'left_iris_x': item['left_iris_x'], + 'left_iris_y': item['left_iris_y'], + 'right_iris_x': item['right_iris_x'], + 'right_iris_y': item['right_iris_y'] + }) + + # Chama a predição com os dois CSVs de calibração + predictions = gaze_tracker.predict( + calib_csv_path, + calib_csv_path, + k + ) + + # Verifica se o retorno é lista, dicionário ou outro + if isinstance(predictions, list): + # Se for lista, adiciona timestamp e metadados a cada item + for i in range(len(predictions)): + predictions[i]['timestamp'] = iris_data[i].get('timestamp') + if screen_height is not None: + predictions[i]['screen_height'] = screen_height + if screen_width is not None: + predictions[i]['screen_width'] = screen_width + elif isinstance(predictions, dict): + # Se for dicionário, anexa metadados gerais (exemplo) + if screen_height is not None: + predictions['screen_height'] = screen_height + if screen_width is not None: + predictions['screen_width'] = screen_width + # Timestamp pode não fazer sentido em dicionário com estrutura complexa + else: + print("Retorno da predição tem tipo inesperado:", type(predictions)) + + return Response(json.dumps(predictions), status=200, mimetype='application/json') + + except Exception as e: + print("Erro na batch_predict:", e) + traceback.print_exc() + return Response("Erro interno na predição", status=500) + # def session_results(): # session_id = request.args.__getitem__('id') diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..91396f55 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "eye-tracker-api", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/requirements.txt b/requirements.txt index 0d1f70ea..69429c32 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,4 +15,6 @@ scipy==1.15.2 six==1.17.0 threadpoolctl==3.6.0 tzdata==2025.2 -Werkzeug==3.1.3 \ No newline at end of file +Werkzeug==3.1.3 +gunicorn==23.0.0 +requests==2.31.0 diff --git a/wsgi.py b/wsgi.py index 6100c8f7..f4846df2 100644 --- a/wsgi.py +++ b/wsgi.py @@ -13,6 +13,5 @@ import os from app.main import app - if __name__ == "__main__": - app.run(debug=True, host="0.0.0.0", port=int(os.environ.get("PORT", 5000))) + app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 8080)))