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
import base64
import io
import tempfile
import time
import uuid
from pathlib import Path
from typing import Any
import cv2
import numpy as np
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 pydantic import BaseModel
@@ -31,20 +29,36 @@ WEB_DIR = Path(__file__).parent
STATIC_DIR = WEB_DIR / "static"
STATIC_DIR.mkdir(exist_ok=True)
# In-memory image store: id → np.ndarray BGR
_IMAGES: dict[str, np.ndarray] = {}
# Cache del last annotated: (image_id, matches_hash) → png bytes
_ANNOT_CACHE: dict[tuple[str, int], bytes] = {}
# Persistenza immagini su disco (sopravvive a restart server)
CACHE_DIR = Path(tempfile.gettempdir()) / "pm2d_cache"
CACHE_DIR.mkdir(exist_ok=True)
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:
iid = uuid.uuid4().hex[:12]
_IMAGES[iid] = img
cv2.imwrite(str(CACHE_DIR / f"{iid}.png"), img)
_IMG_CACHE[iid] = img
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:
ok, buf = cv2.imencode(".png", img)
if not ok:
@@ -257,7 +271,7 @@ async def upload(file: UploadFile = File(...)):
@app.get("/image/{iid}/raw")
def image_raw(iid: str):
img = _IMAGES.get(iid)
img = _load_image(iid)
if img is None:
raise HTTPException(404, "Image not found")
return Response(_encode_png(img), media_type="image/png")
@@ -265,8 +279,8 @@ def image_raw(iid: str):
@app.post("/match", response_model=MatchResp)
def match(p: MatchParams):
model = _IMAGES.get(p.model_id)
scene = _IMAGES.get(p.scene_id)
model = _load_image(p.model_id)
scene = _load_image(p.scene_id)
if model is None or scene is None:
raise HTTPException(404, "Immagini non trovate")
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,
piramide, ecc.) dall'analisi automatica della ROI.
"""
model = _IMAGES.get(p.model_id)
scene = _IMAGES.get(p.scene_id)
model = _load_image(p.model_id)
scene = _load_image(p.scene_id)
if model is None or scene is None:
raise HTTPException(404, "Immagini non trovate")
x, y, w, h = p.roi
@@ -364,7 +378,7 @@ def match_simple(p: SimpleMatchParams):
@app.post("/auto_tune")
def tune(p: TuneParams):
model = _IMAGES.get(p.model_id)
model = _load_image(p.model_id)
if model is None:
raise HTTPException(404, "Immagine non trovata")
x, y, w, h = p.roi