rating_report_utils.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. import io
  2. import json
  3. import cv2
  4. import numpy as np
  5. from fastapi import HTTPException
  6. from datetime import datetime
  7. from app.core.minio_client import minio_client
  8. from app.core.config import settings
  9. from typing import List, Dict, Any, Optional
  10. from PIL import Image
  11. from app.utils.scheme import ImageType
  12. from mysql.connector.pooling import PooledMySQLConnection
  13. from app.core.logger import get_logger
  14. logger = get_logger(__name__)
  15. def parse_json_value(raw_value: Any) -> Dict[str, Any]:
  16. if raw_value is None:
  17. return {}
  18. if isinstance(raw_value, dict):
  19. return raw_value
  20. if isinstance(raw_value, str):
  21. try:
  22. return json.loads(raw_value)
  23. except json.JSONDecodeError:
  24. return {}
  25. return {}
  26. def format_datetime(dt: Any) -> str:
  27. if isinstance(dt, datetime):
  28. return dt.strftime("%Y-%m-%d %H:%M:%S")
  29. return ""
  30. def save_rating_report_history(
  31. db_conn: PooledMySQLConnection,
  32. card_id: int,
  33. card_no: str,
  34. report_name: str,
  35. report_json: Dict[str, Any]
  36. ) -> int:
  37. try:
  38. with db_conn.cursor() as cursor:
  39. insert_sql = (
  40. f"INSERT INTO {settings.RATING_REPORT_HISTORY_TABLE_NAME} "
  41. "(card_id, cardNo, report_name, report_json) "
  42. "VALUES (%s, %s, %s, %s)"
  43. )
  44. cursor.execute(
  45. insert_sql,
  46. (card_id, card_no, report_name, json.dumps(report_json, ensure_ascii=False))
  47. )
  48. db_conn.commit()
  49. return cursor.lastrowid
  50. except Exception as e:
  51. db_conn.rollback()
  52. logger.error(f"保存评级报告历史失败: {e}")
  53. raise HTTPException(status_code=500, detail="保存评级报告历史失败。")
  54. def get_active_json(image_data: Any) -> Optional[Dict]:
  55. """获取有效的json数据,优先 modified_json"""
  56. if not image_data:
  57. return None
  58. # image_data 可能是 Pydantic 对象或 字典,做兼容处理
  59. if hasattr(image_data, "modified_json"):
  60. mj = image_data.modified_json
  61. dj = image_data.detection_json
  62. else:
  63. mj = image_data.get("modified_json")
  64. dj = image_data.get("detection_json")
  65. # 注意:根据 schema.py,这里读出来已经是 dict 了,不需要 json.loads
  66. # 如果数据库里存的是 null,读出来是 None
  67. if mj:
  68. return mj
  69. return dj
  70. def crop_defect_image(original_image_path_str: str, min_rect: List, output_filename: str) -> str:
  71. """
  72. 通过 MinIO 切割缺陷图片为正方形并保存
  73. """
  74. try:
  75. # ★ 将进来的全路径 URL 剥离为相对路径 (如 /Data/xxx.jpg) 供 MinIO 读取
  76. rel_path = original_image_path_str.replace(settings.DATA_HOST_URL, "")
  77. rel_path = "/" + rel_path.lstrip('/\\')
  78. object_name = f"{settings.MINIO_BASE_PREFIX}{rel_path}"
  79. # 1. 从 MinIO 获取原图字节
  80. try:
  81. response = minio_client.get_object(settings.MINIO_BUCKET, object_name)
  82. image_bytes = response.read()
  83. response.close()
  84. response.release_conn()
  85. except Exception as e:
  86. logger.warning(f"从MinIO获取原图失败: {object_name} -> {e}")
  87. return ""
  88. # 2. 在内存中用 PIL 切图
  89. with Image.open(io.BytesIO(image_bytes)) as img:
  90. img_w, img_h = img.size
  91. (center_x, center_y), (rect_w, rect_h), angle = min_rect
  92. center_x = int(center_x)
  93. center_y = int(center_y)
  94. rect_w = int(rect_w)
  95. rect_h = int(rect_h)
  96. resize_scale = 1.5 - abs(abs(angle % 90) - 45) / 90
  97. side_length = max(max(rect_w, rect_h) * resize_scale, 100)
  98. half_side = side_length / 2
  99. left, top = max(0, center_x - half_side), max(0, center_y - half_side)
  100. right, bottom = min(img_w, center_x + half_side), min(img_h, center_y + half_side)
  101. cropped_img = img.crop((left, top, right, bottom))
  102. # 3. 将切割后的图存入内存流,并上传到 MinIO
  103. out_bytes = io.BytesIO()
  104. cropped_img.save(out_bytes, format="JPEG", quality=95)
  105. out_bytes.seek(0)
  106. out_rel_path = f"/DefectImage/{output_filename}"
  107. out_object_name = f"{settings.MINIO_BASE_PREFIX}{out_rel_path}"
  108. minio_client.put_object(
  109. settings.MINIO_BUCKET,
  110. out_object_name,
  111. out_bytes,
  112. len(out_bytes.getvalue()),
  113. content_type="image/jpeg"
  114. )
  115. return settings.get_full_url(out_rel_path)
  116. except Exception as e:
  117. logger.error(f"切割并上传图片失败: {e}")
  118. return ""