Explorar o código

get_card_details接口新增缺陷图

袁威 hai 2 semanas
pai
achega
f5db7e0310
Modificáronse 2 ficheiros con 138 adicións e 0 borrados
  1. 62 0
      app/api/formate_xy.py
  2. 76 0
      app/utils/scheme.py

+ 62 - 0
app/api/formate_xy.py

@@ -16,6 +16,9 @@ from app.utils.scheme import (
 )
 from app.crud import crud_card
 from app.utils.xy_process import convert_internal_to_xy_format, convert_xy_to_internal_format
+import hashlib
+from app.core.minio_client import minio_client
+from app.utils.rating_report_utils import crop_defect_image
 
 logger = get_logger(__name__)
 router = APIRouter()
@@ -28,11 +31,68 @@ class QueryMode(str, Enum):
     prev = "prev"
 
 
+def _process_defects_for_json(card_id: int, img_id: int, img_path: str, json_data: dict, side: str):
+    if not json_data or "result" not in json_data:
+        return
+    defect_result = json_data["result"].get("defect_result", {})
+    defects = defect_result.get("defects", [])
+    
+    defect_detail_list = []
+    for idx, defect in enumerate(defects, start=1):
+        min_rect = defect.get("min_rect")
+        defect_img_url = ""
+        location_str = ""
+        
+        if min_rect and len(min_rect) == 3:
+            center_x, center_y = min_rect[0]
+            location_str = f"{int(center_x)},{int(center_y)}"
+            
+            # 使用坐标哈希作为缓存文件名,避免重复裁剪
+            rect_str = str(min_rect)
+            rect_hash = hashlib.md5(rect_str.encode('utf-8')).hexdigest()[:8]
+            filename = f"xy_{card_id}_{img_id}_{idx}_{rect_hash}.jpg"
+            
+            out_rel_path = f"/DefectImage/{filename}"
+            out_object_name = f"{settings.MINIO_BASE_PREFIX}{out_rel_path}"
+            try:
+                # 检查 MinIO 中是否已有该截图,有则直接使用
+                minio_client.stat_object(settings.MINIO_BUCKET, out_object_name)
+                defect_img_url = settings.get_full_url(out_rel_path)
+            except Exception:
+                # 不存在或异常,则执行裁剪并上传
+                defect_img_url = crop_defect_image(img_path, min_rect, filename)
+        
+        # 1. 给每条缺陷带上 defectImgUrl
+        defect["defectImgUrl"] = defect_img_url
+        
+        # 2. 组装 defectDetailList 元素
+        raw_type = f"{defect.get('defect_type', '')}".upper().strip()
+        type_str_map = {
+            "CORNER": "CORNER",
+            "EDGE": "SIDE",
+            "FACE": "SURFACE"
+        }
+        type_str = type_str_map.get(raw_type, raw_type)
+        
+        defect_detail_list.append({
+            "id": defect.get("id", idx),
+            "side": side,
+            "location": location_str,
+            "type": type_str,
+            "defectImgUrl": defect_img_url,
+            "label": defect.get("label", ""),
+            "actual_area": defect.get("actual_area", 0)
+        })
+        
+    defect_result["defectDetailList"] = defect_detail_list
+
+
 def _process_images_to_xy_format(card_data: dict):
     """
     内部辅助函数:遍历卡牌数据中的图片,将 JSON 格式转换为前端需要的 XY 格式。
     直接修改传入的 card_data 字典。
     """
+    card_id = card_data.get("id")
     if "images" in card_data and card_data["images"]:
         for img in card_data["images"]:
             # 处理 detection_json
@@ -40,6 +100,7 @@ def _process_images_to_xy_format(card_data: dict):
                 d_json = img.detection_json
                 if isinstance(d_json, str):
                     d_json = json.loads(d_json)
+                _process_defects_for_json(card_id, img.id, img.image_path, d_json, img.image_type)
                 # *** 转换逻辑 ***
                 img.detection_json = convert_internal_to_xy_format(d_json)
 
@@ -48,6 +109,7 @@ def _process_images_to_xy_format(card_data: dict):
                 m_json = img.modified_json
                 if isinstance(m_json, str):
                     m_json = json.loads(m_json)
+                _process_defects_for_json(card_id, img.id, img.image_path, m_json, img.image_type)
                 # *** 转换逻辑 ***
                 img.modified_json = convert_internal_to_xy_format(m_json)
     return card_data

+ 76 - 0
app/utils/scheme.py

@@ -199,3 +199,79 @@ class CardListResponseWrapper(BaseModel):
 
 class ReviewUpdate(BaseModel):
     review_state: int = Field(..., ge=1, le=4, description="审核状态 (1待复检, 2已复检, 3审核未通过, 4审核通过)")
+
+
+class CardTable(BaseModel):
+    id: int
+    card_name: Optional[str] = None
+    cardNo: Optional[str] = None
+    card_type: str = "pokemon"
+    detection_score: Optional[float] = None
+    modified_score: Optional[float] = None
+    is_edited: bool = False
+    created_at: datetime
+    updated_at: datetime
+    review_state: int = 1
+
+    class Config:
+        from_attributes = True
+
+    @field_serializer("created_at", "updated_at", check_fields=False)
+    def serialize_datetime(self, dt: datetime, _info):
+        if dt is not None:
+            return dt.strftime("%Y-%m-%d %H:%M:%S")
+        return dt
+
+
+class CardImageTable(BaseModel):
+    id: int
+    card_id: int
+    image_type: str
+    image_name: Optional[str] = None
+    image_path: str
+    detection_image_path: Optional[str] = None
+    modified_image_path: Optional[str] = None
+    detection_json: Dict[str, Any]
+    modified_json: Optional[Dict[str, Any]] = None
+    is_edited: bool = False
+    created_at: datetime
+    updated_at: datetime
+
+    class Config:
+        from_attributes = True
+
+    @field_validator("detection_json", "modified_json", mode="before")
+    @classmethod
+    def parse_json_string(cls, v):
+        if v is None:
+            return None
+        if isinstance(v, str):
+            try:
+                return json.loads(v)
+            except json.JSONDecodeError:
+                raise ValueError("Invalid JSON string in database")
+        return v
+
+    @field_serializer("created_at", "updated_at", check_fields=False)
+    def serialize_datetime(self, dt: datetime, _info):
+        if dt is not None:
+            return dt.strftime("%Y-%m-%d %H:%M:%S")
+        return dt
+
+
+class CardGrayImageTable(BaseModel):
+    id: int
+    card_id: int
+    image_type: str
+    image_path: str
+    created_at: datetime
+    updated_at: datetime
+
+    class Config:
+        from_attributes = True
+
+    @field_serializer("created_at", "updated_at", check_fields=False)
+    def serialize_datetime(self, dt: datetime, _info):
+        if dt is not None:
+            return dt.strftime("%Y-%m-%d %H:%M:%S")
+        return dt