Spaces:
Sleeping
Sleeping
| """OpenCV-based preprocessing for uploaded room photos. | |
| Validates decodability, applies EXIF orientation, resizes so the longest edge is | |
| <= MAX_EDGE_PX (aspect preserved), and writes a normalized copy to disk. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| import cv2 | |
| import numpy as np | |
| from PIL import Image, ImageOps | |
| from ..config import settings | |
| class PreprocessResult: | |
| path: Path # normalized image path on disk | |
| rel_path: str # path relative to the server root (e.g. "static/uploads/x.jpg") | |
| width: int | |
| height: int | |
| def _load_oriented(raw_path: Path) -> Image.Image: | |
| img = Image.open(raw_path) | |
| img = ImageOps.exif_transpose(img) # honor EXIF orientation | |
| return img.convert("RGB") | |
| def preprocess_image( | |
| raw_path: Path, out_path: Path, max_edge: int | None = None | |
| ) -> PreprocessResult: | |
| """Validate, auto-orient, resize and save a normalized image. | |
| Raises ValueError if the file cannot be decoded as an image. | |
| """ | |
| max_edge = max_edge or settings.MAX_EDGE_PX | |
| try: | |
| pil = _load_oriented(raw_path) | |
| except Exception as exc: # noqa: BLE001 | |
| raise ValueError(f"Could not decode image: {raw_path.name}") from exc | |
| arr = cv2.cvtColor(np.array(pil), cv2.COLOR_RGB2BGR) | |
| if arr is None or arr.size == 0: | |
| raise ValueError(f"Empty/invalid image: {raw_path.name}") | |
| h, w = arr.shape[:2] | |
| scale = min(1.0, max_edge / float(max(h, w))) | |
| if scale < 1.0: | |
| w, h = int(round(w * scale)), int(round(h * scale)) | |
| arr = cv2.resize(arr, (w, h), interpolation=cv2.INTER_AREA) | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| cv2.imwrite(str(out_path), arr) | |
| rel = out_path.relative_to(settings.STATIC_DIR).as_posix() | |
| return PreprocessResult(path=out_path, rel_path=f"static/{rel}", width=w, height=h) | |