img-processing / api.py
harsh-dev's picture
Add application file
3ba5b62 unverified
Raw
History Blame Contribute Delete
5.13 kB
import os
import time
import uuid
from dotenv import load_dotenv
load_dotenv()
from flask import Flask, request, send_from_directory, jsonify, session
from flask_cors import CORS
from diff import compare_images
from diff_ai import compare_images_ai
from describe import describe_change
from auth import (
authenticate, login_required, get_secret_key, current_user,
)
app = Flask(__name__)
app.config["SECRET_KEY"] = get_secret_key()
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
# Allow the frontend origin to send cookies. For local dev we list the dev
# origins; in production set DIFFLENS_ORIGINS env var to your domain.
_origins_env = os.environ.get("DIFFLENS_ORIGINS")
if _origins_env:
_origins = [o.strip() for o in _origins_env.split(",") if o.strip()]
else:
_origins = [
"http://localhost:8000", "http://127.0.0.1:8000",
"http://localhost:5000", "http://127.0.0.1:5000",
"http://localhost:3000", "http://127.0.0.1:3000"
]
CORS(app, resources={r"/*": {"origins": _origins}}, supports_credentials=True)
# Use /tmp/uploads on Linux/production environments (e.g. Hugging Face Spaces)
if os.name != "nt" or os.environ.get("HF_HUB_DISABLE_SYMLINKS_WARNING"):
UPLOAD_FOLDER = "/tmp/uploads"
else:
UPLOAD_FOLDER = "../uploads"
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def cleanup_old_files(folder, max_age_seconds=900):
"""Clean up files in the given folder that are older than max_age_seconds."""
try:
now = time.time()
for filename in os.listdir(folder):
filepath = os.path.join(folder, filename)
if os.path.isfile(filepath):
stat = os.stat(filepath)
if now - stat.st_mtime > max_age_seconds:
try:
os.remove(filepath)
except Exception as e:
print(f"[app] Failed to remove {filename}: {e}")
except Exception as e:
print(f"[app] Cleanup failed: {e}")
@app.route("/")
def home():
return "Backend running"
# ---------------- AUTH ----------------
@app.route("/auth/login", methods=["POST", "OPTIONS"])
def login():
if request.method == "OPTIONS":
return ("", 204)
data = request.get_json(silent=True) or {}
username = (data.get("username") or "").strip()
password = data.get("password") or ""
if not username or not password:
return jsonify({"error": "missing credentials"}), 400
if not authenticate(username, password):
return jsonify({"error": "invalid credentials"}), 401
session["user"] = username
session.permanent = True
return jsonify({"user": username})
@app.route("/auth/logout", methods=["POST", "OPTIONS"])
def logout():
if request.method == "OPTIONS":
return ("", 204)
session.clear()
return jsonify({"ok": True})
@app.route("/auth/me", methods=["GET", "OPTIONS"])
def me():
if request.method == "OPTIONS":
return ("", 204)
return jsonify({"user": current_user()})
# ---------------- DIFF ----------------
@app.route("/compare", methods=["POST", "OPTIONS"])
def compare():
if request.method == "OPTIONS":
return jsonify({"status": "ok"}), 200
# Clean up old temp uploads
cleanup_old_files(UPLOAD_FOLDER)
if "image1" not in request.files or "image2" not in request.files:
return jsonify({"error": "Missing images"}), 400
img1 = request.files["image1"]
img2 = request.files["image2"]
# Use UUID to prevent image name collisions from concurrent users
req_id = str(uuid.uuid4())
path1 = os.path.join(UPLOAD_FOLDER, f"{req_id}_img1.jpg")
path2 = os.path.join(UPLOAD_FOLDER, f"{req_id}_img2.jpg")
img1.save(path1)
img2.save(path2)
output_path = os.path.join(UPLOAD_FOLDER, f"{req_id}_result.png")
heatmap_path = os.path.join(UPLOAD_FOLDER, f"{req_id}_heatmap.png")
try:
result_data = compare_images_ai(path1, path2, output_path, heatmap_path)
except Exception as e:
print(f"[app] AI pipeline failed ({e}); falling back to classical")
result_data = compare_images(path1, path2, output_path, heatmap_path)
result_data["pipeline"] = "classical"
description = describe_change(path1, path2, result_data)
# Construct the base URL dynamically so Hugging Face Space works without hardcoded domains
public_base = os.environ.get("DIFFLENS_PUBLIC_URL")
if not public_base:
proto = request.headers.get("X-Forwarded-Proto", request.scheme)
public_base = f"{proto}://{request.host}"
public_base = public_base.rstrip("/")
return jsonify({
"result": f"{public_base}/uploads/{req_id}_result.png",
"heatmap": f"{public_base}/uploads/{req_id}_heatmap.png",
"metrics": result_data,
"description": description,
})
@app.route('/uploads/<filename>')
def uploaded_file(filename):
return send_from_directory(UPLOAD_FOLDER, filename)
if __name__ == "__main__":
port = int(os.environ.get("PORT", 5050))
app.run(debug=True, host="0.0.0.0", port=port)