Spaces:
Sleeping
Sleeping
Download src/app.py from so-21-si/spot-the-diff: direct link, hf CLI and curl.
- Browser
- Download file 6.19 kB
-
https://huggingface.co/spaces/so-21-si/spot-the-diff/resolve/main/src/app.py
- Command line
-
hf download hf://spaces/so-21-si/spot-the-diff/src/app.py
-
curl -L -o app.py https://huggingface.co/spaces/so-21-si/spot-the-diff/resolve/main/src/app.py
6.19 kB
| """Flask application factory.""" | |
| from __future__ import annotations | |
| import logging | |
| import os | |
| from pathlib import Path | |
| from flask import Flask | |
| from src.config import config | |
| from src.database import init_db | |
| from src.routes import register_blueprints | |
| from src.services.segmentation import SegmentationService | |
| from src.services.saliency import SaliencyService | |
| from src.services.inpainting import InpaintingService | |
| from src.services.color_changer import ColorChanger | |
| from src.services.object_duplicator import ObjectDuplicator | |
| from src.services.answer_visualizer import AnswerVisualizer | |
| from src.services.a4_layout_composer import A4LayoutComposer | |
| from src.services.difference_generator import DifferenceGenerator | |
| from src.services.job_manager import JobManager | |
| from src.utils.file_manager import ensure_directories | |
| def create_app(config_name=None) -> Flask: | |
| """Create and configure Flask application. | |
| Args: | |
| config_name: Configuration name ('development', 'production', or None for auto-detect) | |
| """ | |
| # Set Ultralytics environment variables before any imports | |
| # This prevents "Read-only file system" errors in deployment | |
| os.environ.setdefault('YOLO_CONFIG_DIR', '/tmp/.config/Ultralytics') | |
| os.environ.setdefault('TORCH_HOME', '/tmp/.cache/torch') | |
| if config_name is None: | |
| # Auto-detect environment | |
| config_name = os.environ.get("FLASK_ENV", "development") | |
| if config_name not in config: | |
| config_name = "development" | |
| config_class = config[config_name] | |
| app = Flask( | |
| __name__, | |
| static_folder=str(Path(__file__).parent / "static"), | |
| template_folder=str(Path(__file__).parent / "templates"), | |
| ) | |
| app.config.from_object(config_class) | |
| _setup_logging(app) | |
| _ensure_dirs(app) | |
| _init_security(app) | |
| init_db(app.config["DATABASE_PATH"]) | |
| _init_services(app) | |
| register_blueprints(app) | |
| return app | |
| def _setup_logging(app: Flask) -> None: | |
| level = logging.DEBUG if app.debug else logging.INFO | |
| logging.basicConfig( | |
| level=level, | |
| format="%(asctime)s [%(levelname)s] %(name)s: %(message)s", | |
| datefmt="%Y-%m-%d %H:%M:%S", | |
| ) | |
| def _ensure_dirs(app: Flask) -> None: | |
| ensure_directories( | |
| app.config["UPLOAD_FOLDER"], | |
| app.config["OUTPUT_FOLDER"], | |
| app.config["MODEL_FOLDER"], | |
| ) | |
| def _init_security(app: Flask) -> None: | |
| """Initialize security headers and rate limiting.""" | |
| try: | |
| from flask_talisman import Talisman | |
| except ImportError: | |
| logging.warning("Flask-Talisman not installed. Security headers disabled.") | |
| else: | |
| sec_config = app.config.get("SECURITY_HEADERS", {}) | |
| Talisman( | |
| app, | |
| force_https=sec_config.get("force_https", False), | |
| strict_transport_security=sec_config.get("strict_transport_security", True), | |
| strict_transport_security_max_age=sec_config.get( | |
| "strict_transport_security_max_age", 31536000 | |
| ), | |
| content_security_policy=sec_config.get("content_security_policy"), | |
| content_security_policy_nonce_in=["script-src", "style-src"], | |
| x_content_type_options=sec_config.get("x_content_type_options", True), | |
| frame_options=sec_config.get("x_frame_options", "SAMEORIGIN"), | |
| force_file_save=False, | |
| ) | |
| logging.info("Security headers initialized with Flask-Talisman") | |
| if app.config.get("RATELIMIT_ENABLED", True): | |
| try: | |
| from flask_limiter import Limiter | |
| from flask_limiter.util import get_remote_address | |
| except ImportError: | |
| logging.warning("Flask-Limiter not installed. Rate limiting disabled.") | |
| else: | |
| limiter = Limiter( | |
| get_remote_address, | |
| app=app, | |
| default_limits=[app.config.get("RATELIMIT_DEFAULT", "100 per hour")], | |
| storage_uri=app.config.get("RATELIMIT_STORAGE_URI", "memory://"), | |
| ) | |
| app.extensions["limiter"] = limiter | |
| logging.info("Rate limiting initialized with Flask-Limiter") | |
| def _init_services(app: Flask) -> None: | |
| model_path = os.path.join( | |
| app.config["MODEL_FOLDER"], app.config["FASTSAM_MODEL"] | |
| ) | |
| segmentation = SegmentationService( | |
| model_path=model_path, | |
| conf=app.config["FASTSAM_CONF"], | |
| iou=app.config["FASTSAM_IOU"], | |
| imgsz=app.config["PROCESSING_IMAGE_SIZE"], | |
| ) | |
| # Pre-load FastSAM model at startup to avoid first-request timeout | |
| # This takes ~30-60 seconds but prevents WORKER TIMEOUT on first request | |
| # Only pre-load if model file exists (skips on first deployment) | |
| from pathlib import Path | |
| model_file = Path(model_path) | |
| if model_file.exists(): | |
| logging.info("Pre-loading FastSAM model at startup...") | |
| segmentation._ensure_model() | |
| logging.info("FastSAM model pre-loaded successfully") | |
| else: | |
| logging.warning( | |
| "FastSAM model not found at startup. Model will be loaded on first request. " | |
| "Consider downloading model before deployment: python scripts/download_model.py" | |
| ) | |
| saliency = SaliencyService() | |
| inpainting = InpaintingService(radius=app.config["INPAINT_RADIUS"]) | |
| color_changer = ColorChanger() | |
| duplicator = ObjectDuplicator() | |
| answer_visualizer = AnswerVisualizer() | |
| a4_composer = A4LayoutComposer() | |
| generator = DifferenceGenerator( | |
| segmentation=segmentation, | |
| saliency=saliency, | |
| inpainting=inpainting, | |
| color_changer=color_changer, | |
| object_duplicator=duplicator, | |
| difficulty_config=app.config["DIFFICULTY_CONFIG"], | |
| segment_min_area_ratio=app.config["SEGMENT_MIN_AREA_RATIO"], | |
| segment_max_area_ratio=app.config["SEGMENT_MAX_AREA_RATIO"], | |
| ) | |
| job_manager = JobManager( | |
| generator=generator, | |
| answer_visualizer=answer_visualizer, | |
| a4_composer=a4_composer, | |
| output_folder=app.config["OUTPUT_FOLDER"], | |
| database_path=app.config["DATABASE_PATH"], | |
| max_workers=app.config["MAX_WORKERS"], | |
| ) | |
| app.extensions["job_manager"] = job_manager | |