room-visualizer-api / backend /app /modules /input_preprocess.py
Johnntirs's picture
Room Visualizer backend (Docker)
0cdb4e8
Raw
History Blame Contribute Delete
1.89 kB
"""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
@dataclass
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)