import numpy as np from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.db.session import get_db from app.dependencies import get_user_id from app.models.analysis import Analysis from app.models.aoi import AOI from app.schemas.aoi import AOICreate, AOIResult, AOIUpdate from app.services.aoi_analysis import compute_aoi_attention from app.services.storage import storage router = APIRouter(tags=["aoi"]) @router.post("/analyses/{analysis_id}/aois", response_model=list[AOIResult], status_code=201) async def create_aois( analysis_id: str, body: AOICreate, db: AsyncSession = Depends(get_db), user_id: str = Depends(get_user_id), ): stmt = select(Analysis).where(Analysis.id == analysis_id, Analysis.user_id == user_id) result = await db.execute(stmt) analysis = result.scalar_one_or_none() if not analysis: raise HTTPException(status_code=404, detail="Analysis not found") if analysis.status != "completed": raise HTTPException(status_code=400, detail="Analysis not yet completed") # Load saliency map npy_path = storage.get_path(analysis_id, "saliency_raw.npy") if not npy_path.exists(): raise HTTPException(status_code=400, detail="Saliency data not available") saliency = np.load(str(npy_path)) regions = [r.model_dump() for r in body.regions] attention_results = compute_aoi_attention(saliency, regions) aoi_records = [] for region, att in zip(body.regions, attention_results): aoi = AOI( analysis_id=analysis_id, label=region.label, x=region.x, y=region.y, width=region.width, height=region.height, attention_pct=att["attention_pct"], area_pct=att["area_pct"], attention_density=att["attention_density"], ) db.add(aoi) aoi_records.append(aoi) await db.flush() for aoi in aoi_records: await db.refresh(aoi) return [AOIResult.model_validate(aoi) for aoi in aoi_records] @router.get("/analyses/{analysis_id}/aois", response_model=list[AOIResult]) async def list_aois( analysis_id: str, db: AsyncSession = Depends(get_db), user_id: str = Depends(get_user_id), ): stmt = select(Analysis).where(Analysis.id == analysis_id, Analysis.user_id == user_id) result = await db.execute(stmt) analysis = result.scalar_one_or_none() if not analysis: raise HTTPException(status_code=404, detail="Analysis not found") aoi_stmt = select(AOI).where(AOI.analysis_id == analysis_id) aoi_result = await db.execute(aoi_stmt) aois = aoi_result.scalars().all() return [AOIResult.model_validate(aoi) for aoi in aois] @router.delete("/analyses/{analysis_id}/aois/{aoi_id}", status_code=204) async def delete_aoi( analysis_id: str, aoi_id: str, db: AsyncSession = Depends(get_db), user_id: str = Depends(get_user_id), ): # Verify analysis ownership stmt = select(Analysis).where(Analysis.id == analysis_id, Analysis.user_id == user_id) result = await db.execute(stmt) if not result.scalar_one_or_none(): raise HTTPException(status_code=404, detail="Analysis not found") aoi_stmt = select(AOI).where(AOI.id == aoi_id, AOI.analysis_id == analysis_id) aoi_result = await db.execute(aoi_stmt) aoi = aoi_result.scalar_one_or_none() if not aoi: raise HTTPException(status_code=404, detail="AOI not found") await db.delete(aoi)