瀏覽代碼

灰度图保存

AnlaAnla 2 周之前
父節點
當前提交
6728fc9d81
共有 5 個文件被更改,包括 327 次插入206 次删除
  1. 1 1
      .idea/deployment.xml
  2. 5 5
      Test/img_score_and_insert2.py
  3. 164 179
      Test/test02.json
  4. 155 3
      app/api/formate_xy.py
  5. 2 18
      app/crud/crud_card.py

+ 1 - 1
.idea/deployment.xml

@@ -26,7 +26,7 @@
       <paths name="显卡服务器@192.168.77.249">
         <serverdata>
           <mappings>
-            <mapping local="$PROJECT_DIR$" web="/" />
+            <mapping deploy="/ML/BranchProject/CardScoreDataServer-grayImg" local="$PROJECT_DIR$" web="/" />
           </mappings>
         </serverdata>
       </paths>

+ 5 - 5
Test/img_score_and_insert2.py

@@ -7,10 +7,10 @@ from typing import Dict, Any, Tuple, Optional, List
 from datetime import datetime
 
 # --- 配置区域 ---
-INFERENCE_SERVICE_URL = "http://127.0.0.1:7754"
-STORAGE_SERVICE_URL = "http://127.0.0.1:7755"
-# INFERENCE_SERVICE_URL = "http://192.168.77.249:7754"
-# STORAGE_SERVICE_URL = "http://192.168.77.249:7755"
+# INFERENCE_SERVICE_URL = "http://127.0.0.1:7754"
+# STORAGE_SERVICE_URL = "http://127.0.0.1:7755"
+INFERENCE_SERVICE_URL = "http://192.168.77.249:7754"
+STORAGE_SERVICE_URL = "http://192.168.77.249:7755"
 
 # 映射关系:后端 score_type 和 image_type 现在是一致的
 # 灰度图不需要映射,直接透传
@@ -322,7 +322,7 @@ if __name__ == "__main__":
     BASE_PATH = r"C:\Code\ML\Image\Card\_251208_reflect_many_img"
 
     # 模拟循环处理
-    for img_num in range(1, 7):
+    for img_num in range(1, 4):
         print(f">>>>> 处理图片组: {img_num}")
 
         # 构造路径 (假设文件名格式如下,可根据实际修改)

文件差異過大導致無法顯示
+ 164 - 179
Test/test02.json


+ 155 - 3
app/api/formate_xy.py

@@ -11,10 +11,9 @@ from app.core.config import settings
 from app.core.logger import get_logger
 from app.core.database_loader import get_db_connection
 from app.utils.scheme import (
-    CardDetailResponse, IMAGE_TYPE_TO_SCORE_TYPE
+    CardDetailResponse, IMAGE_TYPE_TO_SCORE_TYPE, ImageType
 )
 from app.crud import crud_card
-# 导入新写的工具函数
 from app.utils.xy_process import convert_internal_to_xy_format, convert_xy_to_internal_format
 
 logger = get_logger(__name__)
@@ -221,4 +220,157 @@ async def update_image_modified_json(
         raise HTTPException(status_code=500, detail=f"更新JSON数据失败: {e}")
     finally:
         if cursor:
-            cursor.close()
+            cursor.close()
+
+
+# 处理灰度如
+@router.put("/update/json_gray/{id}", status_code=200, summary="[灰度] 接收xy格式, 合并至Ring图重计算并保存")
+async def update_gray_image_json(
+        id: int,
+        new_json_data: dict = Body(..., description="前端传来的灰度图编辑后的JSON(xy格式)"),
+        db_conn: PooledMySQLConnection = db_dependency
+):
+    """
+    针对灰度图 (front_gray/back_gray) 的保存逻辑。
+    """
+    cursor = None
+
+    # 1. 格式还原
+    internal_gray_json = convert_xy_to_internal_format(new_json_data)
+    gray_defects = internal_gray_json.get("result", {}).get("defect_result", {}).get("defects", [])
+
+    try:
+        cursor = db_conn.cursor(dictionary=True)
+
+        # 2. 获取灰度图信息
+        # 注意:灰度图存在 card_gray_images 表中
+        cursor.execute(f"SELECT card_id, image_type FROM {settings.DB_GRAY_IMAGE_TABLE_NAME} WHERE id = %s", (id,))
+        gray_row = cursor.fetchone()
+
+        if not gray_row:
+            raise HTTPException(status_code=404, detail=f"ID为 {id} 的灰度图未找到。")
+
+        card_id = gray_row['card_id']
+        gray_image_type = gray_row['image_type']
+
+        # 3. 确定目标 Ring 图类型
+        target_ring_type = None
+        if gray_image_type == ImageType.front_gray.value:
+            target_ring_type = ImageType.front_ring.value
+        elif gray_image_type == ImageType.back_gray.value:
+            target_ring_type = ImageType.back_ring.value
+        else:
+            raise HTTPException(status_code=400, detail=f"不支持的灰度图类型: {gray_image_type}")
+
+        # 4. 获取目标 Ring 图数据 (Card Images 表)
+        cursor.execute(
+            f"SELECT id, detection_json, modified_json FROM {settings.DB_IMAGE_TABLE_NAME} "
+            f"WHERE card_id = %s AND image_type = %s",
+            (card_id, target_ring_type)
+        )
+        ring_row = cursor.fetchone()
+
+        if not ring_row:
+            raise HTTPException(status_code=404, detail=f"未找到对应的 Ring 图 ({target_ring_type}),无法应用修改。")
+
+        ring_image_id = ring_row['id']
+        # 优先使用 modified_json,如果没有则使用 detection_json
+        source_json_str = ring_row['modified_json'] if ring_row['modified_json'] else ring_row['detection_json']
+
+        if isinstance(source_json_str, str):
+            ring_json_data = json.loads(source_json_str)
+        else:
+            ring_json_data = source_json_str
+
+        # 5. 合并逻辑 (Merge Logic)
+        # 确保路径存在
+        if "result" not in ring_json_data: ring_json_data["result"] = {}
+        if "defect_result" not in ring_json_data["result"]: ring_json_data["result"]["defect_result"] = {}
+        if "defects" not in ring_json_data["result"]["defect_result"]: ring_json_data["result"]["defect_result"][
+            "defects"] = []
+
+        ring_defects = ring_json_data["result"]["defect_result"]["defects"]
+
+        # 遍历灰度图传来的新缺陷列表
+        for new_defect in gray_defects:
+            gray_id = new_defect.get("gray_id")
+
+            # 只有带有 gray_id 的才进行特殊合并处理 (理论上前端编辑的都应该有,或者新生成的)
+            # 如果没有 gray_id,视作普通新缺陷直接添加
+            if not gray_id:
+                ring_defects.append(new_defect)
+                continue
+
+            # 在 Ring 图现有的缺陷中寻找匹配的 gray_id
+            match_index = -1
+            for i, old_defect in enumerate(ring_defects):
+                if old_defect.get("gray_id") == gray_id:
+                    match_index = i
+                    break
+
+            if match_index != -1:
+                # 存在:替换 (Replace)
+                ring_defects[match_index] = new_defect
+            else:
+                # 不存在:添加 (Append)
+                ring_defects.append(new_defect)
+
+        # 6. 调用计算服务 (对 Ring 图数据进行重算)
+        score_type = IMAGE_TYPE_TO_SCORE_TYPE.get(target_ring_type)  # e.g., 'front_ring'
+
+        logger.info(f"开始重计算 Ring 图分数 (GrayID: {id} -> RingID: {ring_image_id}, Type: {score_type})")
+
+        try:
+            response = await run_in_threadpool(
+                lambda: requests.post(
+                    settings.SCORE_RECALCULATE_ENDPOINT,
+                    params={"score_type": score_type},
+                    json=ring_json_data,  # 发送合并后的 Ring 数据
+                    timeout=20
+                )
+            )
+        except Exception as e:
+            raise HTTPException(status_code=500, detail=f"调用分数计算服务失败: {e}")
+
+        if response.status_code != 200:
+            logger.error(f"分数计算接口返回错误: {response.text}")
+            raise HTTPException(status_code=response.status_code,
+                                detail=f"分数计算接口返回错误: {response.text}")
+
+        final_ring_json = response.json()
+
+        # 7. 保存结果到数据库 (保存到 Ring 图记录)
+        final_json_str = json.dumps(final_ring_json, ensure_ascii=False)
+
+        update_query = (
+            f"UPDATE {settings.DB_IMAGE_TABLE_NAME} "
+            f"SET modified_json = %s, is_edited = TRUE "
+            f"WHERE id = %s"
+        )
+        cursor.execute(update_query, (final_json_str, ring_image_id))
+        db_conn.commit()
+
+        logger.info(f"Ring 图 {ring_image_id} 已根据灰度图 {id} 的修改进行了更新。")
+
+        # 8. 更新卡牌总分状态
+        try:
+            crud_card.update_card_scores_and_status(db_conn, card_id)
+        except Exception as e:
+            logger.error(f"更新卡牌 {card_id} 分数状态失败: {e}")
+
+        return {
+            "detail": f"成功应用灰度图修改到 {target_ring_type}",
+            "target_ring_id": ring_image_id,
+            "gray_id": id
+        }
+
+    except HTTPException:
+        db_conn.rollback()
+        raise
+    except Exception as e:
+        db_conn.rollback()
+        logger.error(f"灰度图更新失败 ({id}): {e}")
+        raise HTTPException(status_code=500, detail=f"系统内部错误: {e}")
+    finally:
+        if cursor:
+            cursor.close()

+ 2 - 18
app/crud/crud_card.py

@@ -88,14 +88,8 @@ def _construct_gray_image_json(gray_type: ImageType, ring_image_data: Optional[D
         if "gray_id" in defect:
             filtered_defects.append(defect)
 
-    # 如果没有找到关联缺陷,返回 None 还是 空结构?
-    # 根据需求:"把它拿过来, 放在它对应的defects 里面"
-    # 如果列表为空,也应该返回一个结构,否则 modified_json 为 None 可能导致误解
-    # 但通常 modified_json 为 None 表示未修改。
-    # 这里我们返回一个带有筛选后缺陷的结构
-
     if not filtered_defects:
-        return None  # 或者返回空结构,视前端需求。这里暂定如果没有相关缺陷,modified_json 为 None
+        return None  # 这里暂定如果没有相关缺陷,modified_json 为 None
 
     gray_modified_json = copy.deepcopy(EMPTY_DETECTION_JSON)
     gray_modified_json["result"]["defect_result"]["defects"] = filtered_defects
@@ -216,8 +210,6 @@ def get_card_list_with_images(
     # 只需要把灰度图的路径也带出来即可。
 
     with db_conn.cursor(dictionary=True) as cursor:
-        # ... (WHERE 构建逻辑完全相同,省略以节省篇幅,直接复制原代码即可) ...
-        # --- 为了完整性,这里简写 query 构建 ---
         query = f"SELECT * FROM {settings.DB_CARD_TABLE_NAME}"
         conditions = []
         params = []
@@ -297,7 +289,6 @@ def get_card_list_with_images(
 
 
 def get_card_list_and_count(
-        # ... (参数同上)
         db_conn: PooledMySQLConnection,
         card_id: Optional[int],
         card_name: Optional[str],
@@ -316,17 +307,10 @@ def get_card_list_and_count(
         skip: int,
         limit: int
 ) -> Dict[str, Any]:
-    # 逻辑与 get_card_list_with_images 基本一致,只是多了 count
-    # 为节省空间,只展示修改了图片查询的部分,Count 和 List Query 的 SQL 构建部分与上文一致
-
     with db_conn.cursor(dictionary=True) as cursor:
-        # ... (SQL WHERE Clause Construction - Same as above) ...
         conditions = []
         params = []
-        # ... (Fill conditions and params - Same as above) ...
 
-        # 假设这里已经有了 where_clause 和 params
-        # --- 为了演示完整逻辑,重新简写构建 ---
         if card_id is not None: conditions.append("id = %s"); params.append(card_id)
         if card_name: conditions.append("card_name LIKE %s"); params.append(f"%{card_name}%")
         if card_type: conditions.append("card_type = %s"); params.append(card_type.value)
@@ -407,4 +391,4 @@ def get_card_list_and_count(
         return {
             "total": total_count,
             "list": cards
-        }
+        }

部分文件因文件數量過多而無法顯示