from datetime import datetime from functools import lru_cache import os import re import time import uuid from flask import Blueprint, current_app, json, request, jsonify from flask_jwt_extended import get_jwt_identity, jwt_required import requests from sqlalchemy import and_, func from app import db from app.models import BagFile, BagStatus, Fst, User, VlmFilter from app import create_app from app.utils.get_vlm_token import Demo from app.utils.driving_tree import vlm_data vlm_bp = Blueprint("vlm", __name__) def extract_datetime_from_filename(bag_filename): """ 从文件名中提取日期和时间。 返回包含年月日时分秒的字符串、datetime对象以及视频URL。 """ match = re.search(r"_(\d{8})-(\d{6})_", bag_filename) if not match: raise ValueError("文件名格式错误,无法提取时间信息") date_part, time_part = match.groups() # 提取年月日时分秒各部分 year = date_part[:4] month = date_part[4:6] day = date_part[6:8] hour = time_part[:2] minute = time_part[2:4] second = time_part[4:6] # 年月日时分秒整合到一个变量(字符串格式) datetime_full = f"{year}-{month}-{day} {hour}:{minute}:{second}" # 原有视频路径相关逻辑保持不变 bag_filename = bag_filename.replace(".bag", "") video_final_path = f"{bag_filename}- Wide.mp4" video_cos = f"momenta/videos/{year}/{month}/{day}/{video_final_path}" video_url = ( "https://data-miningc01-1318950322.cos.ap-shanghai-adc.myqcloud.com/" + video_cos ) # 返回整合后的完整日期时间字符串、datetime对象和视频URL return datetime_full, video_url @lru_cache(maxsize=1) def get_cached_token(): TOKEN_EXPIRE_SECONDS = 7200 """获取缓存的Token,如果过期则重新获取(固定有效期)""" # 获取Token时记录当前时间(作为获取时间) token_info = Demo().getAuthToken() token_info["fetch_time"] = time.time() # 手动添加获取时间 # 检查是否过期(当前时间 - 获取时间 > 有效期) if time.time() - token_info["fetch_time"] >= TOKEN_EXPIRE_SECONDS - 60: # 提前60秒刷新 get_cached_token.cache_clear() new_token = Demo().getAuthToken() new_token["fetch_time"] = time.time() # 记录新的获取时间 return new_token return token_info @vlm_bp.route("/insert-csv", methods=["POST"]) @jwt_required() def insert_db_by_csv(): try: # 1. 获取上传的CSV文件 if "file" not in request.files: return jsonify({"code": 400, "message": "未上传CSV文件"}), 400 file = request.files["file"] if file.filename == "": return jsonify({"code": 400, "message": "未选择文件"}), 400 # 验证文件类型 if not file.filename.endswith(".csv"): return jsonify({"code": 400, "message": "仅支持CSV格式文件"}), 400 # 2. 直接从form中获取表单字段(无需解析JSON) # 验证必要的表单参数 required_fields = ["level1Tag", "status"] form_params = {} for field in required_fields: value = request.form.get(field) if not value: return ( jsonify({"code": 400, "message": f"缺少必要的表单参数: {field}"}), 400, ) form_params[field] = value # 额外获取可选的表单字段 form_params["radioGroup"] = request.form.get("radioGroup", "") form_params["fileKey"] = request.form.get("fileKey", "") # 转换level1Tag为整数 try: level1_tag = int(form_params["level1Tag"]) except ValueError: return jsonify({"code": 400, "message": "level1Tag必须是整数"}), 400 # 验证status是否有效 status_str = form_params["status"] try: status = BagStatus[status_str] # 假设使用枚举类 except KeyError: return ( jsonify({"code": 400, "message": f"无效的status值: {status_str}"}), 400, ) # 替换bag_status if form_params["radioGroup"] == "annotation": bag_status = 0 if form_params["radioGroup"] == "qa": bag_status = 1 # 插入fst_version字段 # 获取当前时间 current_time = datetime.now() # 格式化年月日时,用下划线连接 formatted_time = f"{current_time.year}-{current_time.month:02d}-{current_time.day:02d}-{current_time.hour:02d}" fst_version = "vlm_" + get_jwt_identity() + "_" + formatted_time # 3. 解析CSV文件内容 import csv from io import StringIO # from datetime import datetime # 读取CSV内容 csv_content = file.stream.read().decode("utf-8") csv_file = StringIO(csv_content) csv_reader = csv.DictReader(csv_file) # 验证CSV表头 if "bag_name" not in csv_reader.fieldnames: return ( jsonify({"code": 400, "message": "CSV文件缺少必要的'bag_name'列"}), 400, ) # 4. 组合CSV数据与表单数据,插入数据库 inserted_count = 0 skipped_count = 0 error_records = [] for row_num, row in enumerate( csv_reader, start=2 ): # 行号从2开始(表头为第1行) bag_name = row.get("bag_name", "").strip() if not bag_name: skipped_count += 1 error_records.append( {"row": row_num, "reason": "bag_name为空", "content": row} ) continue # 从文件名提取时间和视频URL(复用你的函数) datetime_full, video_url = extract_datetime_from_filename(bag_name) # 组合数据:CSV的bag_name + 表单的level1Tag、status等 new_bag = BagFile( file_name=bag_name, level1_tag_id=level1_tag, # 表单字段 status=status, # 表单字段 capture_datetime=datetime_full, create_time=datetime.now(), update_time=datetime.now(), bag_status=bag_status, sync_status="SYNC_NOT_READY", user_id=get_jwt_identity(), # 当前用户ID video_url=video_url, fst_version=fst_version, ) db.session.add(new_bag) inserted_count += 1 # 提交事务 db.session.commit() # 5. 返回处理结果 return jsonify( { "code": 200, "message": f"处理完成,成功插入 {inserted_count} 条记录,跳过 {skipped_count} 条记录", "data": { "inserted_count": inserted_count, "skipped_count": skipped_count, "errors": error_records if skipped_count > 0 else None, }, } ) except Exception as e: db.session.rollback() print(f"【上传CSV接口异常】: {str(e)}") return jsonify({"code": 500, "message": f"处理失败:{str(e)}"}), 500 @vlm_bp.route("/get-models", methods=["GET"]) def get_models(): result = { "code": 200, "data": [ { "id": 3, "name": "图片模型Base", "url": "http://10.0.220.110:20080", "type": 0, "remark": "没有finetune的初始模型\nhttp://10.0.220.110:20080", "createUserId": 3, "status": 1, "createdAt": "2024-11-21T07:36:34.337Z", "updatedAt": "2025-04-10T08:00:03.727Z", }, { "id": 4, "name": "图片模型FT4", "url": "http://10.0.220.226:20081", "type": 0, "remark": "第4轮微调模型\nhttp://10.0.220.226:20081", "createUserId": 3, "status": 1, "createdAt": "2025-04-25T07:22:06.986Z", "updatedAt": "2025-05-09T07:00:22.667Z", }, ], "success": True, "message": "success", } # token = get_cached_token() # API_URL = "http://10.0.220.110/api/app/model/models-server?modelType=0" # headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} # response = requests.get(url=API_URL, headers=headers) # result = response.json() return jsonify({"code": 200, "data": result}) @vlm_bp.route("/get-datasets", methods=["GET"]) def get_datasets(): # result = { # "code": 200, # "data": [ # { # "id": 99, # "name": "Momenta", # "path": None, # "type": 0, # "status": 3, # "remark": "这是数据集", # "index_name": "0_33f0ba5e-d00d-4033-b2d4-3a491a347f25", # "create_user_id": None, # "is_delete": 0, # "createdAt": "2025-04-25T09:10:33.072Z", # "updatedAt": "2025-05-19T15:03:33.366Z", # "file_count": None, # }, # { # "id": 107, # "name": "MB", # "path": None, # "type": 0, # "status": 3, # "remark": "这是数据集", # "index_name": "0_abcb005e-6782-4b91-877e-4b1cebcf52e7", # "create_user_id": None, # "is_delete": 0, # "createdAt": "2025-05-15T09:49:23.056Z", # "updatedAt": "2025-05-19T11:35:21.063Z", # "file_count": None, # }, # { # "id": 124, # "name": "MB_DC_2508", # "path": None, # "type": 0, # "status": 3, # "remark": "这是数据集", # "index_name": "0_35caf344-a572-40d1-bc31-e426b785eaa2", # "create_user_id": None, # "is_delete": 0, # "createdAt": "2025-08-29T01:30:34.199Z", # "updatedAt": "2025-09-12T07:45:58.658Z", # "file_count": None, # }, # { # "id": 125, # "name": "MB_DC_2504_06", # "path": None, # "type": 0, # "status": 3, # "remark": "这是数据集", # "index_name": "0_46805e78-738e-439d-9b65-2b5a9cdd5ee5", # "create_user_id": None, # "is_delete": 0, # "createdAt": "2025-09-04T09:54:12.411Z", # "updatedAt": "2025-09-11T19:37:03.399Z", # "file_count": None, # }, # { # "id": 126, # "name": "MB_DC_2507", # "path": None, # "type": 0, # "status": 3, # "remark": "这是数据集", # "index_name": "0_14b37c68-cf81-4c5b-b232-43323c7501e7", # "create_user_id": None, # "is_delete": 0, # "createdAt": "2025-09-11T01:47:57.010Z", # "updatedAt": "2025-09-12T03:45:47.002Z", # "file_count": None, # }, # ], # "success": True, # "message": "success", # } token = get_cached_token() API_URL = "http://10.0.220.110/api/app/model/datasets-list?modelId=4" headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} response = requests.get(url=API_URL, headers=headers) result = response.json() return jsonify({"code": 200, "data": result}) @vlm_bp.route("/get-search", methods=["POST"]) def get_search_list(): # result = vlm_data API_URL = "http://10.0.220.110/api/app/search/4" data = request.json result = requests.post(url=API_URL, json=data) result=result.json() return jsonify({"code": 200, "data": result}) @vlm_bp.route("/get-alltags", methods=["GET"]) def get_alltags(): try: # 查询所有标签 all_tags = Fst.query.all() # 转换为所需的结构 result = [] for tag in all_tags: tag_data = { "id": tag.id, "name": tag.name, "level": tag.level, "parent_id": tag.parent_id, } result.append(tag_data) # 返回JSON响应 return jsonify(result), 200 except Exception as e: # 错误处理 return jsonify({"error": str(e)}), 500 @vlm_bp.route("/insert-vlm-filter", methods=["POST"]) @jwt_required() def add_vlm_filter(): """ 新增VlmFilter记录 请求参数: { "level1": { "id": 407, "name": "TOLL_STATION", "level": 1 }, "level2": { "id": 616, "name": "TOLL_STATION_LEAVE", "level": 2 }, "level3": { "id": 579, "name": "TOLL_STATION_LEAVE_PASSING_NO_LANE_MARKING_SQUARE_AFTER_ROD", "level": 3 }, "level4": null, "bagname": "PL162802_event_hmi_console_event_20250827-182132_0.bag", "comment": "12345", "status": 0, "video_url":"", } """ try: # 1. 获取当前用户ID(从JWT中提取) current_user_id = get_jwt_identity() # 2. 解析请求参数 params = request.json # 3. 验证必要参数 required_fields = ["level1", "level2", "level3", "bagname", "status"] if not all(key in params for key in required_fields): return ( jsonify( { "code": 400, "message": f"缺少必要参数: {', '.join(required_fields)}", } ), 400, ) # 4. 提取参数值 level1 = params["level1"] level2 = params["level2"] level3 = params["level3"] level4 = params.get("level4") bagname = params["bagname"] comment = params.get("comment", "") status = params["status"] video_url = params["video_url"] # 5. 提取各级标签ID level1_tag_id = level1.get("id") if level1 else None level2_tag_id = level2.get("id") if level2 else None level3_tag_id = level3.get("id") if level3 else None level4_tag_id = level4.get("id") if level4 else None # 6. 验证标签层级关系和存在性 # 验证一级标签 if level1_tag_id is not None: level1_tag = Fst.query.get(level1_tag_id) if not level1_tag: return ( jsonify( { "code": 400, "message": f"一级标签不存在(ID: {level1_tag_id})", } ), 400, ) # 验证二级标签 if level2_tag_id is not None: level2_tag = Fst.query.get(level2_tag_id) if not level2_tag: return ( jsonify( { "code": 400, "message": f"二级标签不存在(ID: {level2_tag_id})", } ), 400, ) if not level1_tag_id: return ( jsonify( {"code": 400, "message": "二级标签存在时,一级标签不能为空"} ), 400, ) if level2_tag.parent_id != level1_tag_id: return ( jsonify( { "code": 400, "message": f"二级标签的父级ID({level2_tag.parent_id})与一级标签ID({level1_tag_id})不匹配", } ), 400, ) # 验证三级标签 if level3_tag_id is not None: level3_tag = Fst.query.get(level3_tag_id) if not level3_tag: return ( jsonify( { "code": 400, "message": f"三级标签不存在(ID: {level3_tag_id})", } ), 400, ) if not level2_tag_id: return ( jsonify( {"code": 400, "message": "三级标签存在时,二级标签不能为空"} ), 400, ) if level3_tag.parent_id != level2_tag_id: return ( jsonify( { "code": 400, "message": f"三级标签的父级ID({level3_tag.parent_id})与二级标签ID({level2_tag_id})不匹配", } ), 400, ) # 验证四级标签 if level4_tag_id is not None: level4_tag = Fst.query.get(level4_tag_id) if not level4_tag: return ( jsonify( { "code": 400, "message": f"四级标签不存在(ID: {level4_tag_id})", } ), 400, ) if not level3_tag_id: return ( jsonify( {"code": 400, "message": "四级标签存在时,三级标签不能为空"} ), 400, ) if level4_tag.parent_id != level3_tag_id: return ( jsonify( { "code": 400, "message": f"四级标签的父级ID({level4_tag.parent_id})与三级标签ID({level3_tag_id})不匹配", } ), 400, ) # 7. 创建新的VlmFilter记录 new_vlm = VlmFilter( bag_name=bagname, level1_tag_id=level1_tag_id, level2_tag_id=level2_tag_id, level3_tag_id=level3_tag_id, level4_tag_id=level4_tag_id, comment=comment, user_id=current_user_id, # collection_time=datetime.now(), create_time=datetime.now(), status=status, video_url=video_url, ) # 8. 添加到数据库并提交 db.session.add(new_vlm) db.session.commit() # 9. 返回成功响应 return ( jsonify( { "code": 200, "message": "数据插入成功", "data": { "id": new_vlm.id, "bag_name": new_vlm.bag_name, "level1_tag_id": new_vlm.level1_tag_id, "level2_tag_id": new_vlm.level2_tag_id, "level3_tag_id": new_vlm.level3_tag_id, "level4_tag_id": new_vlm.level4_tag_id, "user_id": new_vlm.user_id, "create_time": new_vlm.create_time.strftime( "%Y-%m-%d %H:%M:%S" ), "status": new_vlm.status, }, } ), 200, ) except Exception as e: db.session.rollback() return jsonify({"code": 500, "message": f"服务器错误: {str(e)}"}), 500 @vlm_bp.route("/get-vlm-filter-list", methods=["POST"]) @jwt_required() def get_vlm_filter_list(): try: # 1. 解析请求参数 params = request.get_json() or {} # 提取分页参数,设置默认值 page = params.get("page", 1) per_page = params.get("per_page", 20) # 提取过滤参数 bag_name = params.get("bag_name", "").strip() level1_tag = params.get("level1_tag", "") start_datetime = params.get("start_datetime", "").strip() end_datetime = params.get("end_datetime", "").strip() user_id = params.get("user_id") # 2. 构建基础查询 query = VlmFilter.query # 3. 动态添加过滤条件 conditions = [] # 固定条件:只展示status=0的数据(核心新增) conditions.append(VlmFilter.status == 0) # 袋名模糊查询 if bag_name: conditions.append(VlmFilter.bag_name.like(f"%{bag_name}%")) # 一级标签查询 if level1_tag: try: level1_id = int(level1_tag) conditions.append(VlmFilter.level1_tag_id == level1_id) except ValueError: print(f"【过滤条件】level1_tag转换失败(非整数): {level1_tag}") # 用户ID过滤 if user_id: try: user_id_int = int(user_id) conditions.append(VlmFilter.user_id == user_id_int) except ValueError: print(f"【过滤条件】user_id转换失败(非整数): {user_id}") # 时间范围查询(按create_time) if start_datetime: try: start_dt = datetime.fromisoformat(start_datetime) conditions.append(VlmFilter.create_time >= start_dt) except ValueError: print(f"【过滤条件】开始时间格式错误: {start_datetime}") if end_datetime: try: end_dt = datetime.fromisoformat(end_datetime) conditions.append(VlmFilter.create_time <= end_dt) except ValueError: print(f"【过滤条件】结束时间格式错误: {end_datetime}") # 应用所有条件(包含固定的status=0) if conditions: query = query.filter(and_(*conditions)) # 4. 排序:按create_time降序 query = query.order_by(VlmFilter.create_time.desc()) # 5. 分页查询 pagination = query.paginate(page=page, per_page=per_page, error_out=False) total_items = pagination.total total_pages = pagination.pages # 6. 序列化结果 result_list = [] for item in pagination.items: # 获取标签名称 level1_name = ( Fst.query.get(item.level1_tag_id).name if item.level1_tag_id else None ) level2_name = ( Fst.query.get(item.level2_tag_id).name if item.level2_tag_id else None ) level3_name = ( Fst.query.get(item.level3_tag_id).name if item.level3_tag_id else None ) level4_name = ( Fst.query.get(item.level4_tag_id).name if item.level4_tag_id else None ) # 获取用户名 username = User.query.get(item.user_id).username if item.user_id else None result_list.append( { "id": item.id, "bag_name": item.bag_name, "level1_tag_id": item.level1_tag_id, "level1_tag_name": level1_name, "level2_tag_id": item.level2_tag_id, "level2_tag_name": level2_name, "level3_tag_id": item.level3_tag_id, "level3_tag_name": level3_name, "level4_tag_id": item.level4_tag_id, "level4_tag_name": level4_name, "video_url": item.video_url, "comment": item.comment, "user_id": item.user_id, "username": username, "collection_time": ( item.collection_time.isoformat() if item.collection_time else None ), "create_time": ( item.create_time.isoformat() if item.create_time else None ), "init_label": item.init_label, "status": item.status, # 此处返回的status始终为0 } ) # 7. 返回响应 return jsonify( { "code": 200, "data": result_list, "total": total_items, "page": page, "pages": total_pages, "message": "查询成功(仅返回status=0的数据)", } ) except Exception as e: print(f"【接口异常】: {str(e)}") return ( jsonify( { "code": 500, "data": [], "total": 0, "page": page if "page" in locals() else 1, "pages": 0, "message": f"查询出错:{str(e)}", } ), 500, ) @vlm_bp.route("/send-loacldb", methods=["POST"]) @jwt_required() def send_filter_vlm_localdb(): try: # 1. 解析请求参数 params = request.get_json() or {} # 验证必要参数 if "data" not in params or not isinstance(params["data"], list): return ( jsonify({"code": 400, "message": "缺少必要参数data或data不是数组"}), 400, ) if "add_status" not in params: return jsonify({"code": 400, "message": "缺少必要参数add_status"}), 400 # 提取参数 data_list = params["data"] add_status = params["add_status"] label_status = params["label_status"] # 验证add_status是否为整数 try: add_status = int(add_status) except ValueError: return jsonify({"code": 400, "message": "add_status必须是整数"}), 400 # 初始化统计变量 inserted_count = 0 updated_count = 0 error_records = [] # 获取当前用户ID current_user_id = get_jwt_identity() current_time = datetime.now() # 获取当前时间 current_time = datetime.now() # 格式化年月日时,用下划线连接 formatted_time = f"{current_time.year}-{current_time.month:02d}-{current_time.day:02d}-{current_time.hour:02d}" fst_version = "send_" + get_jwt_identity() + "_" + formatted_time # 2. 循环处理每条数据 for idx, item in enumerate(data_list): try: # print("item",item) # 验证必要字段 if "bag_name" not in item or not item["bag_name"]: error_records.append( { "index": idx, "reason": "缺少bag_name或bag_name为空", "data": item, } ) continue bag_name=item["bag_name"] datetime_full, video_url = extract_datetime_from_filename(bag_name) # 创建新的BagFile记录 new_bag = BagFile( file_name=bag_name, capture_datetime=datetime_full, level1_tag_id=item.get("level1_tag_id"), level2_tag_id=item.get("level2_tag_id"), level3_tag_id=item.get("level3_tag_id"), level4_tag_id=item.get("level4_tag_id"), video_url=item.get("video_url"), comment1=item.get("comment"), user_id=item.get("user_id") or current_user_id, create_time=( datetime.fromisoformat(item["create_time"]) if item.get("create_time") else current_time ), update_time=current_time, fst_version=fst_version, bag_status=add_status, # 0是标注,1是质检 sync_status="SYNC_NOT_READY", status=BagStatus[label_status], ) print("new_bag", new_bag) # 添加到会话 db.session.add(new_bag) inserted_count += 1 # 3. 更新vlm_filter表 # 假设根据bag_name和id进行匹配 vlm_filter = VlmFilter.query.filter_by( id=item.get("id"), bag_name=item["bag_name"] ).first() if vlm_filter: vlm_filter.status = 1 # 1是入库 vlm_filter.init_label = add_status # 0是标注,1是质检 updated_count += 1 else: error_records.append( { "index": idx, "reason": f"未找到对应的vlm_filter记录 (id: {item.get('id')}, bag_name: {item['bag_name']})", "data": item, } ) except Exception as e: error_records.append( {"index": idx, "reason": f"处理错误: {str(e)}", "data": item} ) # 4. 提交事务 db.session.commit() # 5. 返回结果 return jsonify( { "code": 200, "message": f"批量处理完成,成功插入 {inserted_count} 条记录,{len(error_records)} 条记录处理失败", "data": { "inserted_count": inserted_count, "updated_count": updated_count, "error_count": len(error_records), "errors": error_records if error_records else None, }, } ) except Exception as e: db.session.rollback() print(f"【批量插入接口异常】: {str(e)}") return jsonify({"code": 500, "message": f"处理失败:{str(e)}"}), 500