fix: persistenza immagini su disco (sopravvive restart server)

Bug: _IMAGES era dict in-memory, restart server → browser con id cached
riceveva 404 'Immagini non trovate'.

Fix: scrittura PNG in /tmp/pm2d_cache/{id}.png al upload, _load_image()
prova cache memory prima di leggere disco.

Rimossa funzione _store_image duplicata.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-24 10:00:54 +02:00
parent 9fba46d7f7
commit e1a1b956fd
+30 -16
View File
@@ -9,17 +9,15 @@ Endpoint:
""" """
from __future__ import annotations from __future__ import annotations
import base64 import tempfile
import io
import time import time
import uuid import uuid
from pathlib import Path from pathlib import Path
from typing import Any
import cv2 import cv2
import numpy as np import numpy as np
from fastapi import FastAPI, File, HTTPException, UploadFile from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import FileResponse, HTMLResponse, Response, StreamingResponse from fastapi.responses import HTMLResponse, Response
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel from pydantic import BaseModel
@@ -31,20 +29,36 @@ WEB_DIR = Path(__file__).parent
STATIC_DIR = WEB_DIR / "static" STATIC_DIR = WEB_DIR / "static"
STATIC_DIR.mkdir(exist_ok=True) STATIC_DIR.mkdir(exist_ok=True)
# In-memory image store: id → np.ndarray BGR # Persistenza immagini su disco (sopravvive a restart server)
_IMAGES: dict[str, np.ndarray] = {} CACHE_DIR = Path(tempfile.gettempdir()) / "pm2d_cache"
# Cache del last annotated: (image_id, matches_hash) → png bytes CACHE_DIR.mkdir(exist_ok=True)
_ANNOT_CACHE: dict[tuple[str, int], bytes] = {}
app = FastAPI(title="PM2D Webapp", version="1.0.0") # Cache in-memory (soft, ricaricata da disco se mancante)
_IMG_CACHE: dict[str, np.ndarray] = {}
def _store_image(img: np.ndarray) -> str: def _store_image(img: np.ndarray) -> str:
iid = uuid.uuid4().hex[:12] iid = uuid.uuid4().hex[:12]
_IMAGES[iid] = img cv2.imwrite(str(CACHE_DIR / f"{iid}.png"), img)
_IMG_CACHE[iid] = img
return iid return iid
def _load_image(iid: str) -> np.ndarray | None:
cached = _IMG_CACHE.get(iid)
if cached is not None:
return cached
p = CACHE_DIR / f"{iid}.png"
if not p.exists():
return None
img = cv2.imread(str(p))
if img is not None:
_IMG_CACHE[iid] = img
return img
app = FastAPI(title="PM2D Webapp", version="1.0.0")
def _encode_png(img: np.ndarray) -> bytes: def _encode_png(img: np.ndarray) -> bytes:
ok, buf = cv2.imencode(".png", img) ok, buf = cv2.imencode(".png", img)
if not ok: if not ok:
@@ -257,7 +271,7 @@ async def upload(file: UploadFile = File(...)):
@app.get("/image/{iid}/raw") @app.get("/image/{iid}/raw")
def image_raw(iid: str): def image_raw(iid: str):
img = _IMAGES.get(iid) img = _load_image(iid)
if img is None: if img is None:
raise HTTPException(404, "Image not found") raise HTTPException(404, "Image not found")
return Response(_encode_png(img), media_type="image/png") return Response(_encode_png(img), media_type="image/png")
@@ -265,8 +279,8 @@ def image_raw(iid: str):
@app.post("/match", response_model=MatchResp) @app.post("/match", response_model=MatchResp)
def match(p: MatchParams): def match(p: MatchParams):
model = _IMAGES.get(p.model_id) model = _load_image(p.model_id)
scene = _IMAGES.get(p.scene_id) scene = _load_image(p.scene_id)
if model is None or scene is None: if model is None or scene is None:
raise HTTPException(404, "Immagini non trovate") raise HTTPException(404, "Immagini non trovate")
x, y, w, h = p.roi x, y, w, h = p.roi
@@ -317,8 +331,8 @@ def match_simple(p: SimpleMatchParams):
Il server deriva i parametri tecnici (num_features, soglie gradiente, Il server deriva i parametri tecnici (num_features, soglie gradiente,
piramide, ecc.) dall'analisi automatica della ROI. piramide, ecc.) dall'analisi automatica della ROI.
""" """
model = _IMAGES.get(p.model_id) model = _load_image(p.model_id)
scene = _IMAGES.get(p.scene_id) scene = _load_image(p.scene_id)
if model is None or scene is None: if model is None or scene is None:
raise HTTPException(404, "Immagini non trovate") raise HTTPException(404, "Immagini non trovate")
x, y, w, h = p.roi x, y, w, h = p.roi
@@ -364,7 +378,7 @@ def match_simple(p: SimpleMatchParams):
@app.post("/auto_tune") @app.post("/auto_tune")
def tune(p: TuneParams): def tune(p: TuneParams):
model = _IMAGES.get(p.model_id) model = _load_image(p.model_id)
if model is None: if model is None:
raise HTTPException(404, "Immagine non trovata") raise HTTPException(404, "Immagine non trovata")
x, y, w, h = p.roi x, y, w, h = p.roi