|
@@ -4,14 +4,19 @@ from mysql.connector.pooling import PooledMySQLConnection
|
|
|
import json
|
|
import json
|
|
|
from datetime import datetime
|
|
from datetime import datetime
|
|
|
import copy
|
|
import copy
|
|
|
|
|
+import io
|
|
|
|
|
+import os
|
|
|
|
|
|
|
|
from app.core.config import settings
|
|
from app.core.config import settings
|
|
|
|
|
+from app.core.minio_client import minio_client
|
|
|
from app.utils.scheme import CardImageResponse, CardType, SortBy, SortOrder, ImageType
|
|
from app.utils.scheme import CardImageResponse, CardType, SortBy, SortOrder, ImageType
|
|
|
from app.utils.card_score_calculate import calculate_scores_from_images, SCORE_SOURCE_IMAGE_TYPES
|
|
from app.utils.card_score_calculate import calculate_scores_from_images, SCORE_SOURCE_IMAGE_TYPES
|
|
|
from app.core.logger import get_logger
|
|
from app.core.logger import get_logger
|
|
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
|
|
|
|
+THUMBNAIL_MAX_SIZE = (320, 320)
|
|
|
|
|
+
|
|
|
# 定义灰度图固定的 detection_json 结构
|
|
# 定义灰度图固定的 detection_json 结构
|
|
|
EMPTY_DETECTION_JSON = {
|
|
EMPTY_DETECTION_JSON = {
|
|
|
"result": {
|
|
"result": {
|
|
@@ -23,6 +28,70 @@ EMPTY_DETECTION_JSON = {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _normalize_storage_rel_path(path: Optional[str]) -> Optional[str]:
|
|
|
|
|
+ if not path:
|
|
|
|
|
+ return None
|
|
|
|
|
+ value = str(path)
|
|
|
|
|
+ if value.startswith(settings.DATA_HOST_URL):
|
|
|
|
|
+ value = value.replace(settings.DATA_HOST_URL, "", 1)
|
|
|
|
|
+ return "/" + value.lstrip("/\\")
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _build_thumbnail_rel_path(image_path: Optional[str]) -> Optional[str]:
|
|
|
|
|
+ rel_path = _normalize_storage_rel_path(image_path)
|
|
|
|
|
+ if not rel_path:
|
|
|
|
|
+ return None
|
|
|
|
|
+ base_name = os.path.basename(rel_path)
|
|
|
|
|
+ stem, _ = os.path.splitext(base_name)
|
|
|
|
|
+ if not stem:
|
|
|
|
|
+ return None
|
|
|
|
|
+ return f"/Thumbnail/{stem}_320.jpg"
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+def _get_or_create_thumbnail_url(image_path: Optional[str]) -> Optional[str]:
|
|
|
|
|
+ rel_path = _normalize_storage_rel_path(image_path)
|
|
|
|
|
+ thumbnail_rel_path = _build_thumbnail_rel_path(image_path)
|
|
|
|
|
+ if not rel_path or not thumbnail_rel_path:
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+ source_object_name = f"{settings.MINIO_BASE_PREFIX}{rel_path}"
|
|
|
|
|
+ thumbnail_object_name = f"{settings.MINIO_BASE_PREFIX}{thumbnail_rel_path}"
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ minio_client.stat_object(settings.MINIO_BUCKET, thumbnail_object_name)
|
|
|
|
|
+ return settings.get_full_url(thumbnail_rel_path)
|
|
|
|
|
+ except Exception:
|
|
|
|
|
+ pass
|
|
|
|
|
+
|
|
|
|
|
+ try:
|
|
|
|
|
+ from PIL import Image
|
|
|
|
|
+
|
|
|
|
|
+ response = minio_client.get_object(settings.MINIO_BUCKET, source_object_name)
|
|
|
|
|
+ image_bytes = response.read()
|
|
|
|
|
+ response.close()
|
|
|
|
|
+ response.release_conn()
|
|
|
|
|
+
|
|
|
|
|
+ with Image.open(io.BytesIO(image_bytes)) as img:
|
|
|
|
|
+ if img.mode not in ("RGB", "L"):
|
|
|
|
|
+ img = img.convert("RGB")
|
|
|
|
|
+ img.thumbnail(THUMBNAIL_MAX_SIZE)
|
|
|
|
|
+ out_bytes = io.BytesIO()
|
|
|
|
|
+ img.save(out_bytes, format="JPEG", quality=85)
|
|
|
|
|
+ out_bytes.seek(0)
|
|
|
|
|
+
|
|
|
|
|
+ minio_client.put_object(
|
|
|
|
|
+ settings.MINIO_BUCKET,
|
|
|
|
|
+ thumbnail_object_name,
|
|
|
|
|
+ out_bytes,
|
|
|
|
|
+ len(out_bytes.getvalue()),
|
|
|
|
|
+ content_type="image/jpeg"
|
|
|
|
|
+ )
|
|
|
|
|
+ return settings.get_full_url(thumbnail_rel_path)
|
|
|
|
|
+ except Exception as e:
|
|
|
|
|
+ logger.warning("生成缩略图失败: source=%s error=%s", source_object_name, e)
|
|
|
|
|
+ return None
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
def update_card_scores_and_status(db_conn: PooledMySQLConnection, card_id: int):
|
|
def update_card_scores_and_status(db_conn: PooledMySQLConnection, card_id: int):
|
|
|
"""
|
|
"""
|
|
|
更新cards表中的分数和状态。注意:只基于 card_images 表(主4张图)计算。
|
|
更新cards表中的分数和状态。注意:只基于 card_images 表(主4张图)计算。
|
|
@@ -229,6 +298,7 @@ def get_card_with_details(db_conn: PooledMySQLConnection, card_id: int) -> Optio
|
|
|
|
|
|
|
|
# 处理主图片,补充全路径
|
|
# 处理主图片,补充全路径
|
|
|
for row in main_image_records:
|
|
for row in main_image_records:
|
|
|
|
|
+ row['thumbnail_path'] = _get_or_create_thumbnail_url(row.get('image_path'))
|
|
|
row['image_path'] = settings.get_full_url(row.get('image_path'))
|
|
row['image_path'] = settings.get_full_url(row.get('image_path'))
|
|
|
row['detection_image_path'] = settings.get_full_url(row.get('detection_image_path'))
|
|
row['detection_image_path'] = settings.get_full_url(row.get('detection_image_path'))
|
|
|
row['modified_image_path'] = settings.get_full_url(row.get('modified_image_path'))
|
|
row['modified_image_path'] = settings.get_full_url(row.get('modified_image_path'))
|
|
@@ -277,6 +347,7 @@ def get_card_with_details(db_conn: PooledMySQLConnection, card_id: int) -> Optio
|
|
|
"card_id": row['card_id'],
|
|
"card_id": row['card_id'],
|
|
|
"image_type": g_type,
|
|
"image_type": g_type,
|
|
|
"image_path": settings.get_full_url(row['image_path']),
|
|
"image_path": settings.get_full_url(row['image_path']),
|
|
|
|
|
+ "thumbnail_path": _get_or_create_thumbnail_url(row.get('image_path')),
|
|
|
"created_at": row['created_at'],
|
|
"created_at": row['created_at'],
|
|
|
"updated_at": row['updated_at'],
|
|
"updated_at": row['updated_at'],
|
|
|
"detection_json": virtual_detection_json,
|
|
"detection_json": virtual_detection_json,
|