From 3b5a9e863524747e128a65f3f89eb101cdf46682 Mon Sep 17 00:00:00 2001 From: Luna Date: Sat, 11 Apr 2026 16:33:45 +0200 Subject: [PATCH] Add buffered crop UI and API controls --- app/main.py | 33 ++++++++++++++++++++++++--------- app/vision.py | 6 ++++-- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/main.py b/app/main.py index e0e5e6b..88cf28d 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, File, HTTPException, UploadFile +from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi.responses import HTMLResponse, StreamingResponse from app.config import settings @@ -33,30 +33,45 @@ def index():
+ +

       
Result
- +
+
+
Crop
+ +
+
+
Annotated source
+ +
+
@@ -66,23 +81,23 @@ def index(): @app.post("/api/focus") -async def focus(file: UploadFile = File(...)): +async def focus(file: UploadFile = File(...), buffer_ratio: float = Form(0.15)): from app.vision import process_image try: payload = await file.read() - return process_image(payload, file.filename or "upload") + return process_image(payload, file.filename or "upload", buffer_ratio=buffer_ratio) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc @app.post("/api/focus/image") -async def focus_image(file: UploadFile = File(...)): +async def focus_image(file: UploadFile = File(...), buffer_ratio: float = Form(0.15)): from app.vision import process_image try: payload = await file.read() - result = process_image(payload, file.filename or "upload") + result = process_image(payload, file.filename or "upload", buffer_ratio=buffer_ratio) return StreamingResponse(result["crop_bytes_io"], media_type=result["mime_type"]) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) from exc diff --git a/app/vision.py b/app/vision.py index c79f21b..0863aad 100644 --- a/app/vision.py +++ b/app/vision.py @@ -89,6 +89,7 @@ def detect_salient_object(image: np.ndarray) -> BBox | None: def square_bbox(bbox: BBox, image_shape: tuple[int, int, int], buffer_ratio: float = 0.15) -> BBox: image_h, image_w = image_shape[:2] + buffer_ratio = max(0.0, min(buffer_ratio, 0.5)) side = int(round(max(bbox.w, bbox.h) * (1 + buffer_ratio * 2))) side = max(1, min(side, image_w, image_h)) @@ -126,10 +127,10 @@ def _data_url(image_bytes: bytes, mime_type: str) -> str: return f"data:{mime_type};base64,{base64.b64encode(image_bytes).decode('ascii')}" -def process_image(image_bytes: bytes, filename: str) -> dict[str, Any]: +def process_image(image_bytes: bytes, filename: str, buffer_ratio: float = 0.15) -> dict[str, Any]: image = decode_image(image_bytes) bbox, method = select_primary_bbox(image) - square = square_bbox(bbox, image.shape) + square = square_bbox(bbox, image.shape, buffer_ratio=buffer_ratio) crop = crop_image(image, square) annotated = draw_square(image, square) @@ -139,6 +140,7 @@ def process_image(image_bytes: bytes, filename: str) -> dict[str, Any]: return { "filename": filename, "method": method, + "buffer_ratio": buffer_ratio, "detected_bbox": bbox.__dict__, "square_bbox": square.__dict__, "source_size": {"width": int(image.shape[1]), "height": int(image.shape[0])},