File size: 5,781 Bytes
081bb6f
89b8989
 
9684df5
 
a608ddf
 
 
 
 
 
 
 
89b8989
 
 
 
 
 
9684df5
a608ddf
 
 
 
 
 
 
 
 
 
9684df5
 
 
 
 
 
 
 
 
 
 
 
 
89b8989
 
 
 
 
 
 
 
 
 
a608ddf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9684df5
 
 
 
 
a608ddf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9684df5
 
 
 
 
a608ddf
 
 
 
 
 
 
 
 
 
 
 
9684df5
 
 
 
 
 
 
 
 
a608ddf
 
 
 
 
 
 
 
 
 
 
 
9684df5
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# 檔案路徑: app/routers/ai_router.py

from fastapi import APIRouter, File, UploadFile, HTTPException
from pydantic import BaseModel
from typing import Dict, Any, List, Optional
import logging

# 導入新的整合服務
from ..services.integrated_food_analysis_service import analyze_food_image_integrated

# 設置日誌
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

router = APIRouter(
    prefix="/ai",
    tags=["AI"],
)

# 新增 Pydantic 模型定義
class IntegratedAnalysisResponse(BaseModel):
    success: bool
    analysis_time: float
    food_analysis: Dict[str, Any]
    reference_analysis: Dict[str, Any]
    weight_analysis: Dict[str, Any]
    nutrition_analysis: Dict[str, Any]
    summary: Dict[str, Any]
    architecture: Dict[str, str]

class WeightEstimationResponse(BaseModel):
    food_type: str
    estimated_weight: float
    weight_confidence: float
    weight_error_range: List[float]
    nutrition: Dict[str, Any]
    reference_object: Optional[str] = None
    note: str

class FoodAnalysisResponse(BaseModel):
    food_name: str
    nutrition_info: Dict[str, Any]

@router.post("/analyze-food-image/")
async def analyze_food_image_endpoint(file: UploadFile = File(...)):
    """
    這個端點接收使用者上傳的食物圖片,使用 AI 模型進行辨識,
    並返回辨識出的食物名稱。
    """
    # 檢查上傳的檔案是否為圖片格式
    if not file.content_type or not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="上傳的檔案不是圖片格式。")

    try:
        # 讀取圖片數據
        image_bytes = await file.read()
        
        # 使用新的整合服務進行分析
        result = analyze_food_image_integrated(image_bytes, debug=False)
        
        if not result.get("success", False):
            raise HTTPException(status_code=500, detail=result.get("error_message", "分析失敗"))
        
        # 返回簡化的結果
        return {
            "food_name": result["food_analysis"]["food_name"],
            "nutrition_info": result["nutrition_analysis"]["adjusted_nutrition"]
        }
        
    except Exception as e:
        logger.error(f"食物分析失敗: {str(e)}")
        raise HTTPException(status_code=500, detail=f"分析失敗: {str(e)}")

@router.post("/analyze-food-image-with-weight/", response_model=WeightEstimationResponse)
async def analyze_food_image_with_weight_endpoint(file: UploadFile = File(...)):
    """
    整合食物辨識、重量估算與營養分析的端點。
    使用新的架構:FOOD101 → YOLO(參考物) → SAM+DPT → USDA API
    """
    # 檢查上傳的檔案是否為圖片格式
    if not file.content_type or not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="上傳的檔案不是圖片格式。")

    try:
        # 讀取圖片數據
        image_bytes = await file.read()
        
        # 使用新的整合服務進行分析
        result = analyze_food_image_integrated(image_bytes, debug=False)
        
        if not result.get("success", False):
            raise HTTPException(status_code=500, detail=result.get("error_message", "分析失敗"))
        
        # 轉換為舊格式以保持向後兼容
        weight_analysis = result["weight_analysis"]
        nutrition_analysis = result["nutrition_analysis"]
        
        return WeightEstimationResponse(
            food_type=result["food_analysis"]["food_name"],
            estimated_weight=weight_analysis["estimated_weight"],
            weight_confidence=weight_analysis["weight_confidence"],
            weight_error_range=weight_analysis["weight_error_range"],
            nutrition=nutrition_analysis["adjusted_nutrition"],
            reference_object=weight_analysis["reference_object"],
            note=f"使用新架構分析,耗時 {result['analysis_time']} 秒"
        )
        
    except Exception as e:
        logger.error(f"重量估算分析失敗: {str(e)}")
        raise HTTPException(status_code=500, detail=f"分析失敗: {str(e)}")

@router.post("/analyze-food-image-integrated/")
async def analyze_food_image_integrated_endpoint(file: UploadFile = File(...)):
    """
    新的整合分析端點,返回完整的分析結果
    架構:FOOD101 → YOLO(參考物) → SAM+DPT → USDA API
    """
    # 檢查上傳的檔案是否為圖片格式
    if not file.content_type or not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="上傳的檔案不是圖片格式。")

    try:
        # 讀取圖片數據
        image_bytes = await file.read()
        
        # 使用新的整合服務進行分析
        result = analyze_food_image_integrated(image_bytes, debug=False)
        
        return result
        
    except Exception as e:
        logger.error(f"整合分析失敗: {str(e)}")
        raise HTTPException(status_code=500, detail=f"分析失敗: {str(e)}")

@router.get("/health")
async def health_check():
    """
    健康檢查端點,確認 AI 服務是否正常運作
    """
    return {
        "status": "healthy",
        "services": {
            "food_classification": "available (FOOD101)",
            "reference_detection": "available (YOLO)",
            "weight_estimation": "available (SAM+DPT)",
            "nutrition_api": "available (USDA)",
            "integrated_analysis": "available"
        },
        "architecture": {
            "layer_1": "FOOD101 (食物識別)",
            "layer_2": "YOLO (參考物偵測)",
            "layer_3": "SAM+DPT (重量計算)",
            "layer_4": "USDA API (營養查詢)",
            "layer_5": "重量調整 (營養計算)"
        }
    }